diff --git a/.claude/agents/docs-api-writer.md b/.claude/agents/docs-api-writer.md new file mode 100644 index 00000000..20da3efd --- /dev/null +++ b/.claude/agents/docs-api-writer.md @@ -0,0 +1,141 @@ +--- +name: docs-api-writer +description: Generates the API reference section of the Docusaurus site by walking the tethysext.atcore Python package and writing MDX files with class/function signatures and docstrings extracted directly from the source. Use when the API reference needs to be (re)generated after the docs-scaffolder has set up the website/, or when source-level changes (new modules, renamed classes, edited docstrings) require the reference to be refreshed. +tools: Read, Write, Edit, Bash, Glob, Grep +model: sonnet +--- + +# docs-api-writer + +You produce the **API reference** section of the Docusaurus site under `website/docs/api/` by extracting structured information directly from the Python source in `tethysext/atcore/`. You do not use Sphinx, autodoc, pydoc-markdown, or any external doc generator — you parse the source yourself (using Python's `ast` module via a script you write) and emit MDX. This keeps the toolchain to a single framework: Docusaurus. + +## Hard constraints + +- **No Sphinx**, no `pydoc-markdown`, no `lazydocs`. Pure Python `ast` + MDX templating. +- **Output goes only under `website/docs/api/`.** Do not touch narrative docs. +- **MDX, not MD.** Files end in `.mdx` so future agents can embed React components if needed. +- **Faithful to source.** Never invent parameters, return types, or behavior that isn't in the code or docstrings. If a docstring is missing, say so explicitly: leave a `> _No description._` block — do not paraphrase the function name into prose. +- **Every public symbol gets a stable anchor.** Use Docusaurus's heading-id syntax (`{#class-name-method-name}`) so narrative docs can deep-link to it. + +## Source of truth + +The package: `/Users/nswain/Codes/tethysext-atcore/tethysext/atcore/` + +Top-level subpackages you must cover: +- `cli` +- `controllers` (and its sub-packages `app_users`, `resource_workflows`, `resources`, `rest`) +- `exceptions` +- `forms` +- `gizmos` +- `handlers.py` +- `mixins` +- `models` (and its sub-packages) +- `permissions` +- `resources` +- `services` (and its sub-packages) +- `urls` +- `utilities.py` + +Skip: +- `tests/` and any `__pycache__/`, `*.egg-info/` +- Private modules (filename starts with `_` and isn't `__init__.py`) + +## Workflow + +1. **Write a Python generator script** at `website/scripts/generate_api_docs.py`. It must: + - Walk `tethysext/atcore/` recursively. + - Use `ast.parse` (NOT `import` — never execute the project's code; we are not running its dependencies). Extract classes, functions, methods, signatures, decorators, base classes, and docstrings. + - For each module, write one `.mdx` file under `website/docs/api//.mdx` mirroring the package layout. + - For each subpackage, write an `index.mdx` summarizing the modules within and a matching `_category_.json` controlling sidebar order/label. + - At the API root, write `website/docs/api/index.mdx` linking to each subpackage. + - Emit Docusaurus front-matter at the top of each file: `id`, `title`, `sidebar_label`, `sidebar_position` (use a deterministic alphabetic order). + +2. **MDX format per module file:** + + ```mdx + --- + id: services.app_users.django_user_services + title: tethysext.atcore.services.app_users.django_user_services + sidebar_label: django_user_services + --- + + # `tethysext.atcore.services.app_users.django_user_services` + + + + ## Classes + + ### `ClassName(BaseA, BaseB)` \{#classname} + + + + #### `method_name(self, arg1, arg2='default') → ReturnType` \{#classname-method-name} + + + + **Parameters** + - `arg1` — + - `arg2` — + + ## Functions + + ### `function_name(...) → ...` \{#function-name} + + + ``` + + - Render type hints from the AST exactly as they appear (don't normalize or re-format). + - Decorators (e.g., `@classmethod`, `@staticmethod`, `@property`) appear as a small italic tag above the heading: `*classmethod*`. + - Inheritance: list base classes in the heading. If a base class is internal to the package, link to its anchor. + +3. **Run the script** from the repo root: + + ```bash + python website/scripts/generate_api_docs.py + ``` + + The script must be idempotent: running it twice produces the same output. + +4. **Update `website/sidebars.js`** so the `apiSidebar` autogenerates from `docs/api/`. If the scaffolder already configured this, leave it alone. + +5. **Build the site** to confirm MDX is valid: + + ```bash + cd website && npm run build + ``` + + If the build fails because of MDX-unfriendly characters in docstrings (`<`, `>`, `{`, `}`), fix the generator's escaping rather than hand-editing files. Common gotchas: + - Wrap raw `<` / `>` in backticks. + - Escape `{` / `}` as `\{` / `\}` outside code blocks. + - Escape backslashes in regex docstrings. + +## Determinism + +The generator must produce stable output across runs: +- Sort modules, classes, methods alphabetically (or by source order if you prefer — pick one and document it in a script comment). +- Use `LF` line endings. +- Don't write timestamps, generation hashes, or "last updated" lines into the MDX. + +## What to verify before reporting + +- `npm run build` succeeds with the new pages. +- Every public module under `tethysext/atcore/` (excluding tests and `_*`) has a corresponding `.mdx` file. +- A spot-check of three random files shows their docstrings match what's in the source. + +## Reporting + +Output: + +1. Path to the generator script. +2. Module count and file count produced. +3. Any source-level issues you noticed (e.g., classes with no docstring at all, broken type-hint syntax, etc.) — list as findings, not as fix-it items unless the user asks. +4. `npm run build` outcome. + +Keep the report under 400 words. + +## What you must NOT do + +- Do not write tutorial content, conceptual guides, or how-tos. That's for `docs-narrative-writer`. +- Do not import the project's modules to introspect them. Use `ast` only. The project has heavy runtime deps (Tethys, GDAL, etc.) and the build environment for the docs site won't have them. +- Do not invent return types or parameter descriptions. If the docstring is silent, the docs are silent. +- Do not commit changes. Stage them for the coordinator. diff --git a/.claude/agents/docs-coordinator.md b/.claude/agents/docs-coordinator.md new file mode 100644 index 00000000..32f31002 --- /dev/null +++ b/.claude/agents/docs-coordinator.md @@ -0,0 +1,116 @@ +--- +name: docs-coordinator +description: Orchestrates the docs-scaffolder, docs-api-writer, and docs-narrative-writer agents in the correct order, then fact-checks every claim in the produced documentation against the actual Python source. Has authority to edit any output. Use when standing up the docs site for the first time, when refreshing all docs after a sweep of source changes, or when documentation drift is suspected. +tools: Agent, Read, Write, Edit, Bash, Glob, Grep +model: opus +--- + +# docs-coordinator + +You are the editor-in-chief of the `tethysext-atcore` documentation. You dispatch the three specialist agents in the right order, integrate their reports, and run a thorough fact-check pass against the Python source. You have authority to edit any file under `website/` — but you exercise that authority with care: small, targeted fixes are preferred over rewrites, and large rewrites should be sent back to the originating agent. + +## Hard constraints + +- **Source of truth: `tethysext/atcore/` Python source.** Not the README, not your priors about Tethys, not "what would make sense." The code wins. +- **Do not commit anything.** Stage files; report status; let the user decide when to commit. +- **You cannot dispatch agents in parallel when later agents depend on earlier ones' output.** The order below is mandatory. +- **Never silence a problem by deleting the page.** If a fact is wrong, fix it or replace it with a `:::caution Verification needed` admonition naming the specific question. + +## Orchestration order + +Run agents sequentially: + +1. **`docs-scaffolder`** — only if `website/` does not yet exist, or if the user has explicitly asked for a fresh scaffold. Wait for it to finish, confirm `npm run build` passed, then proceed. +2. **`docs-api-writer`** — generates MDX under `website/docs/api/`. Wait for completion. Confirm files exist and the build still passes. +3. **`docs-narrative-writer`** — writes guides under `website/docs/`. Run only after the API reference exists, so it can cross-link. + +If any agent reports failure, stop and surface the failure to the user before proceeding. Do not paper over a broken scaffold or a failed build by skipping ahead. + +When dispatching a subagent, pass it: +- A self-contained prompt that names the specific scope (don't assume it remembers prior turns). +- The relevant file paths in `tethysext/atcore/` it should focus on (when applicable). +- Any feedback from your prior fact-check pass that it should incorporate. + +## Fact-check pass + +After all three agents have run, do a verification sweep. This is the unique value you add. Work through these checks in order: + +### 1. Imports and symbol existence + +For every code block in narrative docs (`website/docs/` excluding `api/`): +- Extract `from tethysext.atcore... import X` lines. +- Confirm `X` exists in the named module — use `Grep` for `^class X\b` / `^def X\b` in the corresponding file. +- For each attribute access on an atcore object (`obj.method(...)`), confirm `method` is defined on the relevant class (search ancestors if needed). + +If a symbol is missing: edit the doc to use the real name (if it's a typo / rename) or wrap the example in `:::caution Verification needed` with the specific question. + +### 2. Signature truth + +For every function or method shown with a signature in narrative docs: +- Pull the actual `def` line from the source. +- Compare parameter names and order, default values, and return-type annotations. + +If they diverge, edit the narrative doc to match the source. Never edit the source to match the doc. + +### 3. API reference parity + +For every `.mdx` page under `website/docs/api/`: +- Spot-check 5 random class entries: their `(BaseClasses)` heading must match the source's `class Name(BaseClasses):` line. +- Spot-check 5 random method entries: their docstring must literally match the source's docstring (modulo MDX escaping). If the API page paraphrases, treat that as a generator bug — re-dispatch `docs-api-writer` with the specific finding rather than hand-editing. + +### 4. Cross-links + +- Walk every Markdown link in narrative docs. Resolve each one — file existence and (for `#anchor` links) anchor existence. Docusaurus will catch broken file links via `onBrokenLinks: 'throw'`, but anchor checks are weaker — verify them yourself. +- Every concept page must be linked to from at least one how-to or tutorial. Every how-to should link to at least one concept page. Pages that float alone are usually wrong. + +### 5. Install steps + +The Getting Started → Installation page must agree with `pyproject.toml` (Python version, name, declared deps), `Dockerfile` (system packages), and `install.yml` (Tethys install hooks). If they disagree, fix the doc. + +### 6. Configuration claims + +If the docs say "add `'foo'` to `INSTALLED_APPS`," verify by grepping the source and templates for actual usage of `foo`. Stale install instructions are the single most common doc rot — bias toward over-checking here. + +### 7. Build & link integrity + +End with: + +```bash +cd website && npm run build +``` + +The build must succeed. If `onBrokenLinks: 'throw'` is configured, any failure here is a structural bug to fix, not a warning to suppress. + +## Editing authority + +You may: +- Fix typos, broken links, factual errors, and signature mismatches in any `.md` / `.mdx` file under `website/`. +- Add `:::caution Verification needed` admonitions when you cannot resolve a question without user input. +- Tighten prose that is verbose or contradicts another page. + +You may NOT: +- Restructure the IA without re-dispatching `docs-narrative-writer`. +- Regenerate API pages by hand — that's `docs-api-writer`'s job; re-dispatch it. +- Change the Docusaurus config or workflow file — that's `docs-scaffolder`'s job; re-dispatch it. +- Touch source files in `tethysext/atcore/`. Ever. + +## Reporting + +Produce a final report with these sections: + +1. **Pipeline status** — which agents ran, success/failure each. +2. **Build status** — final `npm run build` result. +3. **Fact-check findings** — each issue you found, categorized: + - **Fixed:** what you edited and where. + - **Re-dispatched:** which agent, with what feedback. + - **Open questions:** anything you wrapped in `:::caution Verification needed`, with the precise question for the user. +4. **Surface area summary** — file counts (API pages, narrative pages), top-level structure, deploy URL. + +Keep the full report under 800 words. The user should be able to skim it in two minutes and know exactly what's true, what's verified, and what still needs their attention. + +## What you must NOT do + +- Do not fabricate corroboration. "I verified X" must mean you actually grepped for X. +- Do not run agents in parallel when dependencies require ordering. +- Do not commit, push, deploy, or otherwise externalize the docs without explicit user instruction. +- Do not silently delete content. Every removal goes in the report. diff --git a/.claude/agents/docs-narrative-writer.md b/.claude/agents/docs-narrative-writer.md new file mode 100644 index 00000000..936cbf45 --- /dev/null +++ b/.claude/agents/docs-narrative-writer.md @@ -0,0 +1,129 @@ +--- +name: docs-narrative-writer +description: Writes the narrative documentation — getting-started guide, conceptual overviews, how-to guides, and tutorials — for the entire tethysext-atcore library. Use after the docs-scaffolder has created the website/ shell and (ideally) after docs-api-writer has produced API references that narrative pages can link to. +tools: Read, Write, Edit, Bash, Glob, Grep +model: sonnet +--- + +# docs-narrative-writer + +You write the prose documentation for `tethysext-atcore` — everything except the auto-generated API reference. Your output goes under `website/docs/` (and its subdirectories), in `.md` or `.mdx` format, organized in a Diátaxis-flavored structure that covers the **whole library**. + +## Hard constraints + +- **Stay under `website/docs/`**, but never under `website/docs/api/` (that's owned by `docs-api-writer`). +- **Do not invent APIs.** Every code example, class name, function signature, import path, and configuration option must correspond to something that actually exists in `tethysext/atcore/`. If you're unsure, grep the source. If still unsure, leave a `:::caution Verification needed` admonition rather than guessing. +- **Link, don't duplicate.** When you reference a class or function, link to its API page (`/docs/api/...#anchor`) rather than re-stating its full signature. +- **MDX-safe.** Same escaping rules as the API agent: backtick `<`, `>`, escape `{` / `}` outside code blocks. + +## Information sources, in order of trust + +1. The Python source under `tethysext/atcore/` — always authoritative. +2. The repo's `README.md`, `CITATION.cff`, `pyproject.toml`, `install.yml`, `Dockerfile`, `helm/`, `.github/workflows/`, and `tethysext/atcore/tests/` — useful for install steps, dependencies, and real-world usage examples. +3. Generated API reference under `website/docs/api/` — useful for cross-linking and confirming signatures. +4. Existing comments and docstrings in the source. + +You should not browse the web or assume facts about Tethys Platform itself beyond what is verifiable from the repo. If a Tethys concept needs explanation, link out to the Tethys Platform docs (https://docs.tethysplatform.org) rather than re-explaining it inline. + +## Information architecture + +Produce this structure under `website/docs/`. Each directory gets a `_category_.json` with an explicit `position` so the sidebar order is stable. + +``` +website/docs/ +├── intro.md # Replace the scaffolder's placeholder +├── getting-started/ +│ ├── _category_.json # position: 1 +│ ├── installation.md +│ ├── configuration.md # settings.py additions, environment vars +│ └── first-app.md # minimum-viable Tethys app using atcore +├── concepts/ +│ ├── _category_.json # position: 2 +│ ├── overview.md # what atcore is, what it isn't +│ ├── app-users.md # the app_users system +│ ├── resources.md # Resource model + lifecycle +│ ├── resource-workflows.md # ResourceWorkflow + steps + results +│ ├── controllers.md # base controllers, MapView, ResourceView +│ ├── services.md # spatial managers, model database, condor +│ ├── gizmos.md # SlideSheet, SpatialReferenceSelect +│ ├── permissions.md # permission groups + decorators +│ └── file-database.md # FileDatabase model + connection +├── how-to/ +│ ├── _category_.json # position: 3 +│ ├── add-a-resource-type.md +│ ├── build-a-resource-workflow.md +│ ├── customize-a-map-view.md +│ ├── add-a-rest-endpoint.md +│ ├── run-a-condor-workflow-job.md +│ └── extend-the-spatial-manager.md +├── tutorials/ +│ ├── _category_.json # position: 4 +│ └── walkthrough.md # end-to-end tutorial: build a small atcore app +└── reference/ + ├── _category_.json # position: 5 + ├── permissions-cheatsheet.md + └── exceptions.md # the exceptions module — when each is raised +``` + +If the source has no implementation behind a planned page (e.g., a feature you can't find), drop the page rather than write a stub. Better to ship a smaller doc set than fill it with `TODO`. + +## Page conventions + +Each page begins with Docusaurus front-matter: + +```mdx +--- +id: +title: +sidebar_label: <short label> +sidebar_position: <integer> +--- +``` + +- **Voice:** active, second person, present tense. "You configure...", not "One can configure..." or "We will configure...". +- **Length:** prefer concise. A how-to should fit on one screen of scrolling when possible. A concept page can run longer if necessary. +- **Code blocks:** always specify the language (`python`, `bash`, `yaml`, `mdx`). Snippets must be runnable or clearly marked as illustrative with `# example`. +- **Imports in examples:** use the actual import paths from `tethysext.atcore.*`. Verify them with grep before writing. +- **Admonitions:** use `:::note`, `:::tip`, `:::caution`, `:::danger` for callouts. Use `:::caution Verification needed` for any claim you couldn't fully verify. + +## Workflow + +1. **Survey first.** Before writing any page, spend a pass reading: + - `README.md` + - `pyproject.toml` (for dependencies / Python version) + - `tethysext/atcore/__init__.py` and each subpackage `__init__.py` + - The directory tree under `tethysext/atcore/` + - Selected test files in `tethysext/atcore/tests/` — tests are the best source of "how is this actually used." + - The generated API reference under `website/docs/api/` if it exists + +2. **Build the structure.** Create all directories and `_category_.json` files first. This makes broken-link detection easier as you write. + +3. **Write inside-out.** Start with `concepts/` (they ground everything else), then `how-to/` (which references concepts), then `getting-started/` and `tutorials/` last (which reference both). + +4. **Cross-link aggressively.** When you mention a class, link to `/docs/api/<path>#<anchor>`. When you mention a concept, link to its concept page. + +5. **Verify the build.** + + ```bash + cd website && npm run build + ``` + + The build is configured with `onBrokenLinks: 'throw'`, so any dead link will fail the build. Fix all broken links before reporting done. + +## Reporting + +Output: + +1. Tree of files written (just paths). +2. Pages dropped from the planned structure and why (e.g., "no rest controller for X exists, dropped `add-a-rest-endpoint.md`"). +3. List of any `:::caution Verification needed` admonitions left in the docs, with the question each represents — these are explicit hand-offs to the coordinator. +4. `npm run build` outcome. + +Keep the report under 500 words. + +## What you must NOT do + +- Do not generate or modify files under `website/docs/api/` — that's owned by `docs-api-writer`. +- Do not modify the Docusaurus config or sidebar files unless absolutely required to add a new top-level category. Prefer `_category_.json` per directory. +- Do not document features you can't find in the source. Empty docs > wrong docs. +- Do not commit changes. diff --git a/.claude/agents/docs-scaffolder.md b/.claude/agents/docs-scaffolder.md new file mode 100644 index 00000000..2858d688 --- /dev/null +++ b/.claude/agents/docs-scaffolder.md @@ -0,0 +1,91 @@ +--- +name: docs-scaffolder +description: Scaffolds a Docusaurus 3.x documentation site for tethysext-atcore and configures the GitHub Actions workflow that publishes it to GitHub Pages. Use when bootstrapping the docs site for the first time, or when the Docusaurus config / deployment workflow needs to be regenerated. +tools: Read, Write, Edit, Bash, Glob, Grep +model: sonnet +--- + +# docs-scaffolder + +You scaffold the Docusaurus 3.x documentation site for the `tethysext-atcore` Python library and wire up GitHub Pages deployment. You do not write API or narrative content — that is the job of `docs-api-writer` and `docs-narrative-writer`. You produce the empty shell those agents fill. + +## Hard constraints + +- **Site location: `website/`** at the repo root. Do NOT use the existing `docs/` directory — it already holds Helm chart artifacts (`index.html`, `index.yaml`, `*.tgz`) for the helm repo. Clobbering it would break the helm distribution. +- **Framework: Docusaurus 3.x only.** No Sphinx, no MkDocs, no docusaurus-plugin-typedoc Python bridges. The API agent generates MDX directly. +- **Hosting: GitHub Pages** via GitHub Actions, deployed from the `gh-pages` branch (or via the official `actions/deploy-pages` workflow — pick the latter when the repo allows Pages from Actions, which is the modern default). +- **Node version: pin to an LTS** (20.x at the time of writing) in both `package.json` engines and the GitHub Actions workflow. +- **Package manager: npm** unless `yarn.lock` or `pnpm-lock.yaml` already exist in the repo (they don't currently). + +## What to produce + +1. **`website/` Docusaurus project** initialized with the classic preset, TypeScript turned off (this repo is Python; keep config files JS), but with proper docs/blog structure: + - `website/docusaurus.config.js` + - `website/sidebars.js` — leave with placeholder structure: `tutorialSidebar` autogenerated from `docs/`, plus a separate `apiSidebar` autogenerated from `docs/api/`. + - `website/package.json` with scripts: `start`, `build`, `serve`, `clear`, `swizzle`, `deploy`, `write-translations`, `write-heading-ids`, `typecheck` (the standard set). + - `website/docs/` — empty directory with a single `intro.md` placeholder so the build works before content is written. + - `website/docs/api/` — empty directory with `_category_.json` set to `{ "label": "API Reference", "position": 99 }` so the API agent can drop generated MDX in. + - `website/src/css/custom.css` — minimal default. + - `website/static/img/` — empty (logo can be added later). + - `website/.gitignore` — standard Docusaurus ignores (`node_modules`, `build`, `.docusaurus`, `.cache-loader`). + +2. **`docusaurus.config.js`** populated with: + - `title: 'tethysext-atcore'` + - `tagline:` short tagline pulled or summarized from the repo's top-level `README.md`. If none is obvious, use `'A Tethys Platform extension providing reusable controllers, services, and gizmos.'` + - `url: 'https://aquaveo.github.io'` (or whatever GitHub org owns the repo — detect via `git config --get remote.origin.url`) + - `baseUrl: '/tethysext-atcore/'` + - `organizationName` and `projectName` matching the GitHub remote + - `trailingSlash: false` + - `onBrokenLinks: 'throw'`, `onBrokenMarkdownLinks: 'warn'` + - Themes: classic. Plugins: none beyond defaults to start. + - Navbar: items linking to `Docs` (intro), `API` (`/docs/api/`), and the GitHub repo. + - Footer: minimal — copyright, link to license. + - Disable the blog (`blog: false` in preset options) unless you find an existing `blog/` directory suggesting one is desired. + +3. **`.github/workflows/docs.yml`** that: + - Triggers on push to `master` for paths `website/**`, `tethysext/**`, and the workflow file itself; also on workflow_dispatch. + - Uses `actions/checkout@v4`, `actions/setup-node@v4` with Node 20, caches npm via `cache: 'npm'` and `cache-dependency-path: website/package-lock.json`. + - Runs `npm ci` and `npm run build` from `website/`. + - Uploads the built site (`website/build`) via `actions/upload-pages-artifact@v3` and deploys with `actions/deploy-pages@v4`. + - Has correct permissions block: `pages: write`, `id-token: write`, `contents: read`. + - Has a single `deploy` environment with `name: github-pages` and `url: ${{ steps.deployment.outputs.page_url }}`. + +4. **`website/README.md`** — short, ~20 lines, explaining how to run `npm install`, `npm start`, `npm run build`, and that the deploy is automated via GitHub Actions on push to master. State that API docs are generated by the `docs-api-writer` agent and that narrative content lives directly under `website/docs/`. + +## Verification before you finish + +Run, in order: + +```bash +cd website +npm install --no-audit --no-fund +npm run build +``` + +The build must succeed. If it does not, fix the config and try again — do not hand back a broken scaffold. If `npm install` fails on the user's machine for environmental reasons (no network, etc.), document the exact error and the commands required to recover, but do NOT delete the scaffold. + +After a successful build, run: + +```bash +ls website/build/index.html +``` + +To confirm the static site rendered. + +## Reporting + +When you finish, output: + +1. Files created (paths only, no content dumps). +2. Result of `npm run build` (success / failure summary, not the full log). +3. The deploy URL pattern the site will be served from once the workflow runs. +4. Any decisions you made that the coordinator should re-verify (e.g., guessed org name, guessed tagline). + +Keep the report under 300 words. + +## What you must NOT do + +- Do not write tutorial content, getting-started guides, or API reference MDX. Leave `website/docs/intro.md` as a single-paragraph placeholder. +- Do not modify any file outside `website/`, `.github/workflows/docs.yml`, or `.gitignore` (only if a Docusaurus pattern is missing). +- Do not commit the changes. Leave them staged for the user / coordinator to review. +- Do not add unrelated dependencies (analytics plugins, search plugins, etc.) — keep the surface area small and let later agents add them deliberately. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..4f6ee639 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,65 @@ +name: Build and deploy documentation + +on: + push: + branches: [master] + paths: + - 'website/**' + - 'tethysext/**' + - '.github/workflows/docs.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between an +# in-progress run and the latest queued run. Do NOT cancel in-progress +# deployments; we want them to complete cleanly. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build Docusaurus site + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: website/package-lock.json + + - name: Install dependencies + working-directory: website + run: npm ci + + - name: Regenerate API docs + run: python website/scripts/generate_api_docs.py + + - name: Build site + working-directory: website + run: npm run build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/build + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index a267c592..ff1247c9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ tethysext/atcore/tests/files/file_collection_client_tests/temp/* tethysext/atcore/tests/files/file_database_client_tests/temp/* tethysext/atcore/tests/files/model_file_database/* tethysext/atcore/__version__.py +.venv/ +.remember/ +apps/ diff --git a/README.md b/README.md index 90927059..3c9bc718 100644 --- a/README.md +++ b/README.md @@ -11,90 +11,117 @@ ### OS Dependencies +On Debian/Ubuntu: + ```bash $ sudo apt update -$ sudo apt install gcc libgdal-dev g++ libhdf5-dev +$ sudo apt install gcc g++ libgdal-dev libhdf5-dev ``` -### Activate tethys environment + +On macOS (Homebrew): ```bash -conda activate tethys +$ brew install gdal hdf5 ``` -### Install for Development: -Run the following command from the same directory as the setup.py +### Create a Python virtual environment + +ATCore is a Tethys Platform extension. The supported way to develop and test it is in a Python venv with `tethys-platform` installed via pip. Python 3.10–3.13 are supported. ```bash -$ tethys install -d +$ python3.12 -m venv .venv +$ source .venv/bin/activate +$ pip install --upgrade pip wheel setuptools ``` -### Install for Production: +### Install Tethys Platform and ATCore dependencies -Run the following command from the same directory as the setup.py +Install Tethys Platform itself, then the rest of ATCore's runtime/test dependencies (mirroring `install.yml`): ```bash -$ tethys install +$ pip install tethys-platform +$ pip install \ + "sqlalchemy>=2" \ + "geoalchemy2>=0.13" \ + "django-select2<8.3.0" \ + django-taggit \ + django-datetime-widget2 \ + condorpy \ + coverage \ + factory_boy \ + filelock \ + flake8 \ + geojson \ + pandas \ + panel \ + param \ + plotly \ + "pyshp>=3.0.0" \ + psycopg2-binary \ + "geoserver-restconfig>=2.0.10" \ + tethys-dataset-services ``` -### settings.py +### Install ATCore for development -Add the following to `INSTALLED_APPS` in your `settings.py` (tethys/tethys_portal/settings.py): +From the repo root (the directory containing `pyproject.toml`): -```python -'datetimewidget', -'django_select2', -'taggit', +```bash +$ pip install -e . ``` # Testing -This extension has two types of tests: unit tests and integrated tests. +This extension has two types of tests: unit tests and integrated tests. Integrated tests need a PostgreSQL+PostGIS database; unit tests do not. -## Setup +## Provision the test database -Some of the tests require a test database. The database must be a PostgreSQL 9.6 or higher with the postgis extension intalled. Create an empty database before hand. The default database connection string is: +Easiest path is a local PostGIS Docker container that mirrors CI. The default connection string in `tethysext/atcore/tests/__init__.py` is `postgresql://tethys_super:pass@172.17.0.1:5438/atcore_tests`; on macOS use `127.0.0.1` instead of `172.17.0.1`. ```bash -'postgresql://tethys_super:pass@172.17.0.1:5435/atcore_tests' +$ docker run -d \ + --name atcore-postgis \ + -e POSTGRES_USER=tethys_super \ + -e POSTGRES_PASSWORD=pass \ + -e POSTGRES_DB=atcore_tests \ + -p 5438:5432 \ + --platform linux/amd64 \ + postgis/postgis:17-3.5 ``` -To specify a custom database connection string, define the `ATCORE_TEST_DATABASE` environment variable: +`--platform linux/amd64` is needed on Apple Silicon — the postgis image does not publish a native arm64 manifest. + +To point the tests at a different database, define `ATCORE_TEST_DATABASE`: ```bash -export ATCORE_TEST_DATABASE="postgresql://<username>:<password>@<ipaddress>:<port>/<dbname>" +$ export ATCORE_TEST_DATABASE="postgresql://<username>:<password>@<host>:<port>/<dbname>" ``` -## Running the Tests +The user given must be a PostgreSQL superuser so the test runner can create/destroy the test database. -To run unit tests: +## Run the tests -```bash -$ coverage run --rcfile=coverage.ini -m unittest -v tethysext.atcore.tests.unit_tests -$ coverage report -``` - -To run integrated tests, install extension in existing installation of Tethys and run: +Run each phase individually so failures are easy to attribute: ```bash -$ t -$ coverage run --rcfile=coverage.ini <TETHYS_HOME>/src/manage.py test tethysext.atcore.tests.integrated_tests -$ coverage report -``` +# Unit tests (no database needed) +$ coverage run --rcfile=coverage.ini -m unittest -v tethysext.atcore.tests.unit_tests -## Linting +# Integrated tests (require ATCORE_TEST_DATABASE) +$ TETHYS_MANAGE=$(python -c "import tethys_portal, os; print(os.path.join(os.path.dirname(tethys_portal.__file__), 'manage.py'))") +$ coverage run -a --rcfile=coverage.ini "$TETHYS_MANAGE" test -v 2 tethysext.atcore.tests.integrated_tests -We are using flake8 to enforce the pep 8 standard. Any change to the rules can be made in the tox.ini file. +# Coverage report +$ coverage report --rcfile=coverage.ini --skip-covered -```bash -$ flake8 [dir] +# Lint +$ flake8 ``` -## Run All Tests - -To run all of the test and linting with cumulative coverage: +`test.sh` wraps all four phases: ```bash -. test.sh </path/to/tethys/manage.py> +$ . test.sh "$TETHYS_MANAGE" ``` ## Minify Scripts diff --git a/install.yml b/install.yml index 6d0b68ee..3db8f96b 100644 --- a/install.yml +++ b/install.yml @@ -15,6 +15,7 @@ requirements: - django>=3.2,<6 - django-select2<8.3.0 - django-taggit + - condorpy - coverage - factory_boy - filelock @@ -24,7 +25,8 @@ requirements: - param - pyshp>=3.0.0 - requests - - sqlalchemy<2 + - sqlalchemy>=2 + - geoalchemy2>=0.13 - panel pip: - django-datetime-widget2 diff --git a/tethysext/atcore/controllers/app_users/add_existing_user.py b/tethysext/atcore/controllers/app_users/add_existing_user.py index 8012f5e3..f1b9e705 100644 --- a/tethysext/atcore/controllers/app_users/add_existing_user.py +++ b/tethysext/atcore/controllers/app_users/add_existing_user.py @@ -2,6 +2,7 @@ from django.shortcuts import redirect, render from django.urls import reverse from django.utils.decorators import method_decorator +from sqlalchemy import select from tethys_apps.utilities import get_active_app from tethys_gizmos.gizmo_options import SelectInput from tethysext.atcore.services.app_users.func import get_display_name_for_django_user @@ -113,7 +114,7 @@ def _handle_modify_user_requests(self, request, user_id=None, *args, **kwargs): # Add user to selected organizations and assign custom_permissions if selected_role not in no_organization_roles: for organization_id in selected_organizations: - organization = create_session.query(_Organization).get(organization_id) + organization = create_session.get(_Organization, organization_id) new_app_user.organizations.append(organization) permissions_manager.assign_user_permission( new_app_user, @@ -133,7 +134,7 @@ def _handle_modify_user_requests(self, request, user_id=None, *args, **kwargs): # Get App Users session = SessionMaker() - app_users = session.query(_AppUser).all() + app_users = session.execute(select(_AppUser)).scalars().all() # Setup portal users select all_app_usernames = [u.username for u in app_users] diff --git a/tethysext/atcore/controllers/app_users/manage_organization_members.py b/tethysext/atcore/controllers/app_users/manage_organization_members.py index 4b8dc78a..9a802f29 100644 --- a/tethysext/atcore/controllers/app_users/manage_organization_members.py +++ b/tethysext/atcore/controllers/app_users/manage_organization_members.py @@ -69,7 +69,7 @@ def _handle_manage_member_request(self, request, organization_id, *args, **kwarg request_app_user = _AppUser.get_app_user_from_request(request, session) # Defaults - organization = session.query(_Organization).get(organization_id) + organization = session.get(_Organization, organization_id) selected_members = [str(u.id) for u in organization.members] members_select_errors = "" is_client = organization.consultant and organization.consultant.is_member(request_app_user) @@ -95,7 +95,7 @@ def _handle_manage_member_request(self, request, organization_id, *args, **kwarg # Add members and assign custom_permissions again for user_id in selected_members: - user = session.query(_AppUser).get(user_id) + user = session.get(_AppUser, user_id) organization.members.append(user) # Persist changes diff --git a/tethysext/atcore/controllers/app_users/manage_organizations.py b/tethysext/atcore/controllers/app_users/manage_organizations.py index 193f1d73..2775184e 100644 --- a/tethysext/atcore/controllers/app_users/manage_organizations.py +++ b/tethysext/atcore/controllers/app_users/manage_organizations.py @@ -9,7 +9,8 @@ # Django from django.http import JsonResponse, HttpResponseForbidden from django.shortcuts import render - +# SQLAlchemy +from sqlalchemy import select # Tethys core from tethys_sdk.permissions import has_permission, permission_required # ATCore @@ -60,7 +61,7 @@ def _handle_get(self, request, *args, **kwargs): # List organizations: admins can see all, everyone else can see only the organizations to which they belong if request_app_user.is_staff() or has_permission(request, 'view_all_organizations'): - organizations = session.query(_Organization).all() + organizations = session.execute(select(_Organization)).scalars().all() else: organizations = request_app_user.get_organizations(session, request, cascade=True) @@ -161,7 +162,7 @@ def _handle_delete(self, request, organization_id): try: request_app_user = _AppUser.get_app_user_from_request(request, session) - organization = session.query(_Organization).get(organization_id) + organization = session.get(_Organization, organization_id) self.perform_custom_delete_operations(request, organization) # Validate permission to delete the organization diff --git a/tethysext/atcore/controllers/app_users/manage_resources.py b/tethysext/atcore/controllers/app_users/manage_resources.py index 57699b9f..a6ab22a5 100644 --- a/tethysext/atcore/controllers/app_users/manage_resources.py +++ b/tethysext/atcore/controllers/app_users/manage_resources.py @@ -279,7 +279,7 @@ def _handle_new_group_from_selected(self, request): # Get child resources for child_id in children: - child_resource = session.query(_Resource).get(child_id) + child_resource = session.get(_Resource, child_id) resource.children.append(child_resource) for organization in child_resource.organizations: @@ -307,7 +307,7 @@ def _handle_delete(self, request, resource_id): session = make_session() try: - resource = session.query(_Resource).get(resource_id) + resource = session.get(_Resource, resource_id) try: self.perform_custom_delete_operations(session, request, resource) except Exception: # noqa: E722 diff --git a/tethysext/atcore/controllers/app_users/manage_users.py b/tethysext/atcore/controllers/app_users/manage_users.py index 5ca1d327..6bf0917f 100644 --- a/tethysext/atcore/controllers/app_users/manage_users.py +++ b/tethysext/atcore/controllers/app_users/manage_users.py @@ -11,6 +11,8 @@ from django.http import JsonResponse from django.shortcuts import render from django.utils.decorators import method_decorator +# SQLAlchemy +from sqlalchemy import select # Tethys core from tethys_sdk.permissions import has_permission, permission_required # ATCore @@ -75,7 +77,9 @@ def _handle_get(self, request): # App admins can see all users of the portal if has_permission(request, 'view_all_users'): # Django users - app_users = session.query(_AppUser).filter(_AppUser.username != request_app_user.username).all() + app_users = session.execute( + select(_AppUser).where(_AppUser.username != request_app_user.username) + ).scalars().all() else: # All others can manage users that belong to their organizations or organizations they consult app_users = request_app_user.get_peers(session, request, include_self=False, cascade=True) @@ -163,7 +167,7 @@ def _handle_delete(self, request, user_id): json_response = {'success': True} session = make_session() try: - app_user = session.query(_AppUser).get(user_id) + app_user = session.get(_AppUser, user_id) django_user = app_user.get_django_user() django_user.delete() session.delete(app_user) @@ -192,7 +196,7 @@ def _handle_remove(self, request, user_id): json_response = {'success': True} session = make_session() try: - app_user = session.query(_AppUser).get(user_id) + app_user = session.get(_AppUser, user_id) permissions_manager.remove_all_permissions_groups(app_user) session.delete(app_user) session.commit() diff --git a/tethysext/atcore/controllers/app_users/mixins.py b/tethysext/atcore/controllers/app_users/mixins.py index efc84933..ce07d439 100644 --- a/tethysext/atcore/controllers/app_users/mixins.py +++ b/tethysext/atcore/controllers/app_users/mixins.py @@ -138,7 +138,7 @@ def get_resource(self, request, resource_id, session=None): request_app_user = _AppUser.get_app_user_from_request(request, session) try: - resource = session.query(_Resource).get(resource_id) + resource = session.get(_Resource, resource_id) # TODO: Let the apps check permissions so anonymous user only has access to app specific resources? if not getattr(settings, 'ENABLE_OPEN_PORTAL', False): @@ -188,7 +188,7 @@ def get_resource(self, request, resource_id, session=None): request_app_user = _AppUser.get_app_user_from_request(request, session) try: for _Resource in _Resources: - resource = session.query(_Resource).get(resource_id) + resource = session.get(_Resource, resource_id) if resource: break diff --git a/tethysext/atcore/controllers/app_users/modify_organization.py b/tethysext/atcore/controllers/app_users/modify_organization.py index 12242c7a..295a828c 100644 --- a/tethysext/atcore/controllers/app_users/modify_organization.py +++ b/tethysext/atcore/controllers/app_users/modify_organization.py @@ -12,6 +12,7 @@ from django.shortcuts import redirect, render from django.urls import reverse # Tethys core +from sqlalchemy import select from sqlalchemy.exc import StatementError from sqlalchemy.orm.exc import NoResultFound from tethys_sdk.permissions import permission_required, has_permission @@ -116,9 +117,9 @@ def _handle_modify_user_requests(self, request, organization_id=None, *args, **k if editing: # Initialize the parameters from the existing consultant try: - organization = session.query(_Organization). \ - filter(_Organization.id == organization_id). \ - one() + organization = session.execute( + select(_Organization).where(_Organization.id == organization_id) + ).scalar_one() except (StatementError, NoResultFound): raise ATCoreException('Unable to find the organization.') @@ -205,7 +206,7 @@ def _handle_modify_user_requests(self, request, organization_id=None, *args, **k if valid and custom_valid: # Lookup existing organization and assign/reset fields if editing: - organization = session.query(_Organization).get(organization_id) + organization = session.get(_Organization, organization_id) organization.name = organization_name organization.license = selected_license organization.active = is_active @@ -223,12 +224,12 @@ def _handle_modify_user_requests(self, request, organization_id=None, *args, **k # Add resources for _Resource in _Resources: for resource_id in selected_resources[_Resource.SLUG]: - resource = session.query(_Resource).get(resource_id) + resource = session.get(_Resource, resource_id) organization.resources.append(resource) # Assign consultant if selected_consultant: - consultant = session.query(_Organization).get(selected_consultant) + consultant = session.get(_Organization, selected_consultant) organization.consultant = consultant else: organization.consultant = None diff --git a/tethysext/atcore/controllers/app_users/modify_resource.py b/tethysext/atcore/controllers/app_users/modify_resource.py index 58ca3986..85dd5c87 100644 --- a/tethysext/atcore/controllers/app_users/modify_resource.py +++ b/tethysext/atcore/controllers/app_users/modify_resource.py @@ -14,6 +14,7 @@ from django.shortcuts import redirect, render from django.urls import reverse # Tethys core +from sqlalchemy import select from tethys_sdk.permissions import permission_required, has_permission from tethys_apps.utilities import get_active_app from tethys_gizmos.gizmo_options import TextInput, SelectInput @@ -179,7 +180,7 @@ def _handle_modify_resource_requests(self, request, resource_id=None, *args, **k if valid and custom_valid: # Look up existing resource if editing: - resource = session.query(_Resource).get(resource_id) + resource = session.get(_Resource, resource_id) if not resource: raise ATCoreException('Unable to find {}'.format( _Resource.DISPLAY_TYPE_SINGULAR.lower() @@ -205,23 +206,23 @@ def _handle_modify_resource_requests(self, request, resource_id=None, *args, **k # Assign project to organizations for organization_id in selected_organizations: - organization = session.query(_Organization).get(organization_id) + organization = session.get(_Organization, organization_id) if organization: resource.organizations.append(organization) if self.enable_relationship_fields: # Assign parents to resource if enable_parents_field and selected_parents: - parents = session.query(_Resource) \ - .filter(_Resource.id.in_(selected_parents)) \ - .all() + parents = session.execute( + select(_Resource).where(_Resource.id.in_(selected_parents)) + ).scalars().all() resource.parents.extend(parents) # Assign children to resource if enable_children_field and selected_children: - children = session.query(_Resource) \ - .filter(_Resource.id.in_(selected_children)) \ - .all() + children = session.execute( + select(_Resource).where(_Resource.id.in_(selected_children)) + ).scalars().all() resource.children.extend(children) # Assign spatial reference id, handling change if editing @@ -259,7 +260,7 @@ def _handle_modify_resource_requests(self, request, resource_id=None, *args, **k # Setup edit form fields if editing: # Get existing resource - resource = session.query(_Resource).get(resource_id) + resource = session.get(_Resource, resource_id) can_edit_resource, msg = self.can_edit_resource(session, request, request_app_user, resource) if not can_edit_resource: @@ -526,14 +527,14 @@ def get_parents_select_options(self, session, request, request_app_user, resourc _Organization = self.get_organization_model() # Resource belonging to user's organization - parents_options_query = session.query(_Resource) \ - .filter(_Resource.organizations.any(_Organization.id.in_(app_user_organizations))) + parents_options_stmt = select(_Resource) \ + .where(_Resource.organizations.any(_Organization.id.in_(app_user_organizations))) # If resource is defined (editing) also exclude that resource if resource is not None: - parents_options_query = parents_options_query.filter(_Resource.id != resource.id) + parents_options_stmt = parents_options_stmt.where(_Resource.id != resource.id) - parents_options = [(p.name, p.id) for p in parents_options_query.all()] + parents_options = [(p.name, p.id) for p in session.execute(parents_options_stmt).scalars().all()] return parents_options def get_child_select_options(self, session, request, request_app_user, resource, app_user_organizations): @@ -554,14 +555,14 @@ def get_child_select_options(self, session, request, request_app_user, resource, _Organization = self.get_organization_model() # Resource belonging to user's organization - children_options_query = session.query(_Resource) \ - .filter(_Resource.organizations.any(_Organization.id.in_(app_user_organizations))) + children_options_stmt = select(_Resource) \ + .where(_Resource.organizations.any(_Organization.id.in_(app_user_organizations))) # If resource is defined (editing) also exclude that resource if resource is not None: - children_options_query = children_options_query.filter(_Resource.id != resource.id) + children_options_stmt = children_options_stmt.where(_Resource.id != resource.id) - children_options = [(c.name, c.id) for c in children_options_query.all()] + children_options = [(c.name, c.id) for c in session.execute(children_options_stmt).scalars().all()] return children_options def handle_srid_changed(self, session, request, request_app_user, resource, old_srid, new_srid): diff --git a/tethysext/atcore/controllers/app_users/modify_user.py b/tethysext/atcore/controllers/app_users/modify_user.py index 4cae9700..0cc9fa0c 100644 --- a/tethysext/atcore/controllers/app_users/modify_user.py +++ b/tethysext/atcore/controllers/app_users/modify_user.py @@ -1,6 +1,7 @@ from django.shortcuts import redirect, render from django.urls import reverse from django.contrib import messages +from sqlalchemy import select from sqlalchemy.exc import StatementError from sqlalchemy.orm.exc import NoResultFound from tethys_apps.decorators import permission_required @@ -88,9 +89,9 @@ def _handle_modify_user_requests(self, request, user_id=None, *args, **kwargs): edit_session = make_session() try: - target_app_user = edit_session.query(_AppUser).\ - filter(_AppUser.id == user_id).\ - one() + target_app_user = edit_session.execute( + select(_AppUser).where(_AppUser.id == user_id) + ).scalar_one() except (StatementError, NoResultFound): messages.warning(request, 'The user could not be found.') @@ -196,7 +197,7 @@ def _handle_modify_user_requests(self, request, user_id=None, *args, **kwargs): # Lookup existing app user and django user if editing: - target_app_user = modify_session.query(_AppUser).get(user_id) + target_app_user = modify_session.get(_AppUser, user_id) django_user = target_app_user.django_user # Reset organizations @@ -229,7 +230,7 @@ def _handle_modify_user_requests(self, request, user_id=None, *args, **kwargs): # Update organizations for selected_organization in selected_organizations: - organization = modify_session.query(_Organization).get(selected_organization) + organization = modify_session.get(_Organization, selected_organization) target_app_user.organizations.append(organization) # Persist changes diff --git a/tethysext/atcore/controllers/resource_workflows/mixins.py b/tethysext/atcore/controllers/resource_workflows/mixins.py index edc7f84c..18ed18cf 100644 --- a/tethysext/atcore/controllers/resource_workflows/mixins.py +++ b/tethysext/atcore/controllers/resource_workflows/mixins.py @@ -1,3 +1,5 @@ +from sqlalchemy import select + from tethysext.atcore.controllers.app_users.mixins import ResourceViewMixin from tethysext.atcore.models.app_users import ResourceWorkflow, ResourceWorkflowStep, ResourceWorkflowResult @@ -37,9 +39,9 @@ def get_workflow(self, request, workflow_id, session=None): session = make_session() try: - workflow = session.query(_ResourceWorkflow). \ - filter(_ResourceWorkflow.id == workflow_id). \ - one() + workflow = session.execute( + select(_ResourceWorkflow).where(_ResourceWorkflow.id == workflow_id) + ).scalar_one() finally: if manage_session: @@ -68,9 +70,9 @@ def get_step(self, request, step_id, session=None): session = make_session() try: - step = session.query(_ResourceWorkflowStep). \ - filter(_ResourceWorkflowStep.id == step_id). \ - one() + step = session.execute( + select(_ResourceWorkflowStep).where(_ResourceWorkflowStep.id == step_id) + ).scalar_one() finally: if manage_session: @@ -110,9 +112,9 @@ def get_result(self, request, result_id, session=None): session = make_session() try: - workflow = session.query(_ResourceWorkflowResult). \ - filter(_ResourceWorkflowResult.id == result_id). \ - one() + workflow = session.execute( + select(_ResourceWorkflowResult).where(_ResourceWorkflowResult.id == result_id) + ).scalar_one() finally: if manage_session: diff --git a/tethysext/atcore/controllers/resource_workflows/workflow_views/table_input_wv.py b/tethysext/atcore/controllers/resource_workflows/workflow_views/table_input_wv.py index 3dc18b7a..c3d5063b 100644 --- a/tethysext/atcore/controllers/resource_workflows/workflow_views/table_input_wv.py +++ b/tethysext/atcore/controllers/resource_workflows/workflow_views/table_input_wv.py @@ -119,16 +119,24 @@ def process_step_data(self, request, session, step, resource, current_url, previ row_count = max(row_count, len(c)) optional_columns = step.options.get('optional_columns', []) + nodata_filled = [] for column in columns: if column in optional_columns and not data[column]: c = [TABLE_DATASET_NODATA] * row_count data.update({column: c}) + nodata_filled.append(column) # Save dataset as new pandas DataFrame dataset = pd.DataFrame(data=data, columns=columns) - # Coerce columns to be the same types as the template dataset - dataset = dataset.astype(template_dataset.dtypes, copy=True) + # Coerce columns to be the same types as the template dataset. + # NODATA-filled optional columns hold floats and must not be coerced + # back to the template's dtype (object), since pandas 3.0+ stringifies + # floats when cast to object. + target_dtypes = template_dataset.dtypes.copy() + for column in nodata_filled: + target_dtypes[column] = 'float64' + dataset = dataset.astype(target_dtypes, copy=True) # Reset the parameter to None for changes to be detected and saved. step.set_parameter('dataset', None) diff --git a/tethysext/atcore/controllers/resources/tabs/workflows_tab.py b/tethysext/atcore/controllers/resources/tabs/workflows_tab.py index bc2b661c..1ed556f2 100644 --- a/tethysext/atcore/controllers/resources/tabs/workflows_tab.py +++ b/tethysext/atcore/controllers/resources/tabs/workflows_tab.py @@ -12,6 +12,7 @@ from django.http import JsonResponse from django.shortcuts import reverse, redirect from django.contrib import messages +from sqlalchemy import select from tethys_sdk.permissions import has_permission from tethysext.atcore.models.app_users import ResourceWorkflow @@ -128,9 +129,11 @@ def get_context(self, request, session, resource, context, *args, **kwargs): ) if not self.show_all_workflows and app_user_role not in self.show_all_workflows_roles: - workflows_query = workflows_query.filter(ResourceWorkflow.creator_id == app_user.id) + workflows_query = workflows_query.where(ResourceWorkflow.creator_id == app_user.id) - workflows = workflows_query.order_by(ResourceWorkflow.date_created.desc()).all() + workflows = session.execute( + workflows_query.order_by(ResourceWorkflow.date_created.desc()) + ).scalars().all() # Build up workflow cards for workflows table workflow_cards = [] @@ -272,7 +275,7 @@ def delete(self, request, resource_id, *args, **kwargs): session = make_session() # Get the workflow - workflow = session.query(ResourceWorkflow).get(workflow_id) + workflow = session.get(ResourceWorkflow, workflow_id) # Delete the workflow session.delete(workflow) @@ -304,6 +307,6 @@ def get_workflows_query(self, request, session, resource, app_user): for child in resource.children: resource_ids.append(child.id) - workflows_query = session.query(ResourceWorkflow) \ - .filter(ResourceWorkflow.resource_id.in_(resource_ids)) + workflows_query = select(ResourceWorkflow) \ + .where(ResourceWorkflow.resource_id.in_(resource_ids)) return workflows_query diff --git a/tethysext/atcore/gizmos/slide_sheet.py b/tethysext/atcore/gizmos/slide_sheet.py index 61b6173a..c6aff940 100644 --- a/tethysext/atcore/gizmos/slide_sheet.py +++ b/tethysext/atcore/gizmos/slide_sheet.py @@ -11,7 +11,7 @@ class SlideSheet(TethysGizmoOptions): """ - Spatial reference select input gizmo. + Slide-out panel gizmo for presenting supplemental page content. """ gizmo_name = 'slide_sheet' diff --git a/tethysext/atcore/handlers.py b/tethysext/atcore/handlers.py index 068b376d..85b69084 100644 --- a/tethysext/atcore/handlers.py +++ b/tethysext/atcore/handlers.py @@ -19,7 +19,7 @@ def panel_rws_handler(document): session = Session() current_step_id = document.request.url_route['kwargs']['step_id'] - current_step = session.query(ResourceWorkflowStep).get(current_step_id) + current_step = session.get(ResourceWorkflowStep, current_step_id) package, p_class = current_step.options['param_class'].rsplit('.', 1) mod = __import__(package, fromlist=[p_class]) diff --git a/tethysext/atcore/job_scripts/update_resource_status.py b/tethysext/atcore/job_scripts/update_resource_status.py index b1c2ca8e..65be2e18 100644 --- a/tethysext/atcore/job_scripts/update_resource_status.py +++ b/tethysext/atcore/job_scripts/update_resource_status.py @@ -39,7 +39,7 @@ def run(resource_db_url: str, resource_db_engine = create_engine(resource_db_url, **db_engine_kwargs) make_resource_db_session = sessionmaker(bind=resource_db_engine) resource_db_session = make_resource_db_session() - resource = resource_db_session.query(resource_class).get(resource_id) + resource = resource_db_session.get(resource_class, resource_id) # Check Status List if len(status_keys) <= 0: diff --git a/tethysext/atcore/models/app_users/app_user.py b/tethysext/atcore/models/app_users/app_user.py index 0784a059..56be54b9 100644 --- a/tethysext/atcore/models/app_users/app_user.py +++ b/tethysext/atcore/models/app_users/app_user.py @@ -1,6 +1,9 @@ import uuid -from sqlalchemy import Column, Boolean, String -from sqlalchemy.orm import relationship, validates, reconstructor +from typing import TYPE_CHECKING, Optional + +from sqlalchemy import Boolean, String, select +from sqlalchemy.orm import Mapped, mapped_column, relationship, validates, reconstructor + from tethysext.atcore.models.types.guid import GUID from tethysext.atcore.services.app_users.func import get_display_name_for_django_user from tethysext.atcore.services.app_users.roles import Roles @@ -8,6 +11,10 @@ from .user_setting import UserSetting from .base import AppUsersBase +if TYPE_CHECKING: + from .organization import Organization + from .resource_workflow import ResourceWorkflow + __all__ = ['AppUser'] @@ -28,17 +35,24 @@ class AppUser(AppUsersBase): __tablename__ = 'app_users_app_users' - id = Column(GUID, primary_key=True, default=uuid.uuid4) - username = Column(String) #: Used to map to Django user object - role = Column(String, nullable=False) - is_active = Column(Boolean, default=True) + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + username: Mapped[Optional[str]] = mapped_column(String) #: Used to map to Django user object + role: Mapped[str] = mapped_column(String, nullable=False) + is_active: Mapped[bool] = mapped_column(Boolean, default=True) # Relationships - organizations = relationship('Organization', - secondary=user_organization_association, - back_populates='members') + organizations: Mapped[list["Organization"]] = relationship( + 'Organization', + secondary=user_organization_association, + back_populates='members', + ) + + settings: Mapped[list[UserSetting]] = relationship('UserSetting', back_populates='user') - settings = relationship('UserSetting', back_populates='user') + workflows: Mapped[list["ResourceWorkflow"]] = relationship( + 'ResourceWorkflow', + back_populates='creator', + ) def __init__(self, *args, **kwargs): """ @@ -120,7 +134,7 @@ def get_app_user_from_request(cls, request, session, redirect_if_invalid=True): else: username = request.user.username - app_user = session.query(cls).filter(cls.username == username).one_or_none() + app_user = session.execute(select(cls).where(cls.username == username)).scalar_one_or_none() return app_user @@ -188,7 +202,7 @@ def get_organizations(self, session, request, as_options=False, cascade=True, co return_value = set() if self.is_staff() or has_permission(request, 'view_all_organizations', user=self.django_user): - user_organizations = session.query(_Organization).all() + user_organizations = session.execute(select(_Organization)).scalars().all() else: user_organizations = self.organizations @@ -247,19 +261,19 @@ def get_resources(self, session, request, of_type=None, cascade=True, for_assign can_get_all = has_permission(request, 'view_all_resources', user=self.django_user) if self.is_staff() or can_get_all: - q = session.query(_Resource) + stmt = select(_Resource) # Other users can only assign resources that belong to their organizations else: _Organization = self.get_organization_model() organization_ids = [o.id for o in self.get_organizations(session, request, cascade=cascade)] - q = session.query(_Resource) \ - .filter(_Resource.organizations.any(_Organization.id.in_(organization_ids))) + stmt = select(_Resource) \ + .where(_Resource.organizations.any(_Organization.id.in_(organization_ids))) if not include_children: - q = q.filter(~_Resource.parents.any()) + stmt = stmt.where(~_Resource.parents.any()) - resources = set(q.all()) + resources = set(session.execute(stmt).scalars().all()) return self.filter_resources(resources) def filter_resources(self, resources): @@ -317,9 +331,9 @@ def get_peers(self, session, request, include_self=False, cascade=False): from tethys_sdk.permissions import has_permission if self.is_staff() or has_permission(request, 'assign_any_user', user=self.django_user): - return session.query(AppUser).\ - filter(AppUser.username != AppUser.STAFF_USERNAME).\ - all() + return session.execute( + select(AppUser).where(AppUser.username != AppUser.STAFF_USERNAME) + ).scalars().all() manageable_users = set() organizations = self.get_organizations(session, request, cascade=cascade) @@ -424,14 +438,14 @@ def get_setting(self, session, key, as_value=False, **kwargs): """ _UserSetting = self._get_user_setting_model() - q = session.query(_UserSetting) \ - .filter(_UserSetting.user_id == self.id) \ - .filter(_UserSetting.key == key) \ + stmt = select(_UserSetting) \ + .where(_UserSetting.user_id == self.id) \ + .where(_UserSetting.key == key) attributes_string = _UserSetting.build_attributes_string(**kwargs) - q = q.filter(_UserSetting._attributes == attributes_string) + stmt = stmt.where(_UserSetting._attributes == attributes_string) - setting = q.one_or_none() + setting = session.execute(stmt).scalar_one_or_none() if as_value: return setting.value if setting else None @@ -448,10 +462,10 @@ def get_all_settings(self, session): """ _UserSetting = self._get_user_setting_model() - q = session.query(_UserSetting) \ - .filter(_UserSetting.user_id == self.id) + stmt = select(_UserSetting) \ + .where(_UserSetting.user_id == self.id) - settings = q.all() + settings = session.execute(stmt).scalars().all() return settings diff --git a/tethysext/atcore/models/app_users/base.py b/tethysext/atcore/models/app_users/base.py index 2aa9a336..188b4a74 100644 --- a/tethysext/atcore/models/app_users/base.py +++ b/tethysext/atcore/models/app_users/base.py @@ -1,4 +1,5 @@ -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import DeclarativeBase -AppUsersBase = declarative_base() +class AppUsersBase(DeclarativeBase): + pass diff --git a/tethysext/atcore/models/app_users/initializer.py b/tethysext/atcore/models/app_users/initializer.py index b2a06d83..15f20eae 100644 --- a/tethysext/atcore/models/app_users/initializer.py +++ b/tethysext/atcore/models/app_users/initializer.py @@ -1,3 +1,4 @@ +from sqlalchemy import select from sqlalchemy.orm import sessionmaker from .base import AppUsersBase from .app_user import AppUser @@ -19,9 +20,9 @@ def initialize_app_users_db(engine, first_time=False, app_user_model=AppUser): Session = sessionmaker(engine) session = Session() - staff_user = session.query(app_user_model).\ - filter(app_user_model.username == app_user_model.STAFF_USERNAME).\ - one_or_none() + staff_user = session.execute( + select(app_user_model).where(app_user_model.username == app_user_model.STAFF_USERNAME) + ).scalar_one_or_none() if not staff_user: new_user = app_user_model( diff --git a/tethysext/atcore/models/app_users/organization.py b/tethysext/atcore/models/app_users/organization.py index 98e3c704..e3d4d3fb 100644 --- a/tethysext/atcore/models/app_users/organization.py +++ b/tethysext/atcore/models/app_users/organization.py @@ -1,13 +1,19 @@ +import datetime import uuid -from sqlalchemy import event -from sqlalchemy import Column, Boolean, String, ForeignKey, DateTime, func -from sqlalchemy.orm import relationship, backref, validates +from typing import TYPE_CHECKING, Optional +from sqlalchemy import event, text +from sqlalchemy import Boolean, String, ForeignKey, DateTime, func +from sqlalchemy.orm import Mapped, mapped_column, relationship, validates from tethysext.atcore.models.types.guid import GUID from tethysext.atcore.services.app_users.licenses import Licenses from tethysext.atcore.mixins import AttributesMixin from .associations import organization_resource_association, user_organization_association from .base import AppUsersBase +if TYPE_CHECKING: + from .app_user import AppUser + from .resource import Resource + __all__ = ['Organization'] @@ -23,26 +29,41 @@ class Organization(AppUsersBase, AttributesMixin): DISPLAY_TYPE_PLURAL = 'Organizations' LICENSES = Licenses() - id = Column(GUID, primary_key=True, default=uuid.uuid4) - parent_id = Column(GUID, ForeignKey('app_users_organizations.id')) - name = Column(String) - type = Column(String) - created = Column(DateTime, default=func.now()) - license = Column(String, nullable=False) - license_expires = Column(DateTime) - active = Column(Boolean, default=True) - archived = Column(Boolean, default=False) - _attributes = Column(String) + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + parent_id: Mapped[Optional[uuid.UUID]] = mapped_column(GUID, ForeignKey('app_users_organizations.id')) + name: Mapped[Optional[str]] = mapped_column(String) + type: Mapped[Optional[str]] = mapped_column(String) + created: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime, default=func.now()) + license: Mapped[str] = mapped_column(String, nullable=False) + license_expires: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime) + active: Mapped[bool] = mapped_column(Boolean, default=True) + archived: Mapped[bool] = mapped_column(Boolean, default=False) + _attributes: Mapped[Optional[str]] = mapped_column(String) # Relationships - resources = relationship('Resource', - secondary=organization_resource_association, - back_populates='organizations') - members = relationship('AppUser', - secondary=user_organization_association, - back_populates='organizations') - clients = relationship('Organization', cascade='all,delete', - backref=backref('consultant', remote_side=[id])) + resources: Mapped[list["Resource"]] = relationship( + 'Resource', + secondary=organization_resource_association, + back_populates='organizations', + ) + members: Mapped[list["AppUser"]] = relationship( + 'AppUser', + secondary=user_organization_association, + back_populates='organizations', + ) + clients: Mapped[list["Organization"]] = relationship( + 'Organization', + cascade='all,delete', + back_populates='consultant', + foreign_keys=[parent_id], + ) + + consultant: Mapped[Optional["Organization"]] = relationship( + 'Organization', + remote_side=[id], + back_populates='clients', + foreign_keys=[parent_id], + ) # Polymorphism __mapper_args__ = { @@ -187,14 +208,14 @@ def receive_before_delete(mapper, connection, target): 'resource_id', resource.id ) - connection.execute(delete_relationship) + connection.execute(text(delete_relationship)) delete_resource = sql_template.format( resource.__tablename__, 'id', resource.id ) - connection.execute(delete_resource) + connection.execute(text(delete_resource)) # Remove users that would be orphaned for member in target.members: @@ -204,11 +225,11 @@ def receive_before_delete(mapper, connection, target): 'app_user_id', member.id ) - connection.execute(delete_relationship) + connection.execute(text(delete_relationship)) delete_resource = sql_template.format( member.__tablename__, 'id', member.id ) - connection.execute(delete_resource) + connection.execute(text(delete_resource)) diff --git a/tethysext/atcore/models/app_users/resource.py b/tethysext/atcore/models/app_users/resource.py index 582bec64..e33a9d8e 100644 --- a/tethysext/atcore/models/app_users/resource.py +++ b/tethysext/atcore/models/app_users/resource.py @@ -1,16 +1,21 @@ import datetime import uuid +from typing import TYPE_CHECKING, Optional from django.utils.text import slugify from django.utils.functional import classproperty -from sqlalchemy import Column, Boolean, DateTime, String -from sqlalchemy.orm import relationship, backref +from sqlalchemy import Boolean, DateTime, String +from sqlalchemy.orm import Mapped, mapped_column, relationship from tethysext.atcore.models.types.guid import GUID from tethysext.atcore.mixins import StatusMixin, AttributesMixin, UserLockMixin, SerializeMixin from .app_user import AppUsersBase from .associations import organization_resource_association, resource_parent_child_association +if TYPE_CHECKING: + from .organization import Organization + from .resource_workflow import ResourceWorkflow + __all__ = ['Resource'] @@ -25,30 +30,46 @@ class Resource(StatusMixin, AttributesMixin, UserLockMixin, SerializeMixin, AppU DISPLAY_TYPE_SINGULAR = 'Resource' DISPLAY_TYPE_PLURAL = 'Resources' - id = Column(GUID, primary_key=True, default=uuid.uuid4) - name = Column(String) - description = Column(String) - type = Column(String) - date_created = Column(DateTime, default=datetime.datetime.utcnow) - created_by = Column(String) - status = Column(String) - public = Column(Boolean, default=False) - _attributes = Column(String) - _user_lock = Column(String) + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + name: Mapped[Optional[str]] = mapped_column(String) + description: Mapped[Optional[str]] = mapped_column(String) + type: Mapped[Optional[str]] = mapped_column(String) + date_created: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime, default=datetime.datetime.utcnow) + created_by: Mapped[Optional[str]] = mapped_column(String) + status: Mapped[Optional[str]] = mapped_column(String) + public: Mapped[bool] = mapped_column(Boolean, default=False) + _attributes: Mapped[Optional[str]] = mapped_column(String) + _user_lock: Mapped[Optional[str]] = mapped_column(String) # Relationships - organizations = relationship('Organization', - secondary=organization_resource_association, - back_populates='resources') + organizations: Mapped[list["Organization"]] = relationship( + 'Organization', + secondary=organization_resource_association, + back_populates='resources', + ) - children = relationship( + children: Mapped[list["Resource"]] = relationship( 'Resource', secondary=resource_parent_child_association, - backref=backref('parents'), + back_populates='parents', primaryjoin=id == resource_parent_child_association.c.parent_id, secondaryjoin=id == resource_parent_child_association.c.child_id, ) + parents: Mapped[list["Resource"]] = relationship( + 'Resource', + secondary=resource_parent_child_association, + back_populates='children', + primaryjoin=id == resource_parent_child_association.c.child_id, + secondaryjoin=id == resource_parent_child_association.c.parent_id, + ) + + workflows: Mapped[list["ResourceWorkflow"]] = relationship( + 'ResourceWorkflow', + back_populates='resource', + cascade='all,delete', + ) + # Polymorphism __mapper_args__ = { 'polymorphic_identity': TYPE, diff --git a/tethysext/atcore/models/app_users/resource_workflow.py b/tethysext/atcore/models/app_users/resource_workflow.py index dd124f26..a7c1685c 100644 --- a/tethysext/atcore/models/app_users/resource_workflow.py +++ b/tethysext/atcore/models/app_users/resource_workflow.py @@ -12,16 +12,23 @@ import datetime as dt from abc import abstractmethod +from datetime import datetime +from typing import TYPE_CHECKING, Optional from django.shortcuts import reverse -from sqlalchemy import Column, ForeignKey, String, DateTime, Boolean -from sqlalchemy.orm import relationship, backref +from sqlalchemy import ForeignKey, String, DateTime, Boolean +from sqlalchemy.orm import Mapped, mapped_column, relationship from tethysext.atcore.models.types import GUID from tethysext.atcore.mixins import AttributesMixin, ResultsMixin, UserLockMixin, SerializeMixin from tethysext.atcore.models.app_users.base import AppUsersBase from tethysext.atcore.models.app_users import ResourceWorkflowStep from tethysext.atcore.models.resource_workflow_steps import FormInputRWS, ResultsResourceWorkflowStep, TableInputRWS +if TYPE_CHECKING: + from tethysext.atcore.models.app_users.resource import Resource + from tethysext.atcore.models.app_users.app_user import AppUser + from tethysext.atcore.models.app_users.resource_workflow_result import ResourceWorkflowResult + log = logging.getLogger(f'tethys.{__name__}') __all__ = ['ResourceWorkflow'] @@ -67,23 +74,34 @@ class ResourceWorkflow(AppUsersBase, AttributesMixin, ResultsMixin, UserLockMixi COMPLETE_STATUSES = ResourceWorkflowStep.COMPLETE_STATUSES - id = Column(GUID, primary_key=True, default=uuid.uuid4) - resource_id = Column(GUID, ForeignKey('app_users_resources.id')) - creator_id = Column(GUID, ForeignKey('app_users_app_users.id')) - type = Column(String) - - name = Column(String) - date_created = Column(DateTime, default=dt.datetime.utcnow) - lock_when_finished = Column(Boolean, default=False) - _attributes = Column(String) - _user_lock = Column(String) - - resource = relationship('Resource', backref=backref('workflows', cascade='all,delete')) - creator = relationship('AppUser', backref='workflows') - steps = relationship('ResourceWorkflowStep', order_by='ResourceWorkflowStep.order', backref='workflow', - cascade='all,delete') - results = relationship('ResourceWorkflowResult', order_by='ResourceWorkflowResult.order', backref='workflow', - cascade='all,delete') + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + resource_id: Mapped[Optional[uuid.UUID]] = mapped_column(GUID, ForeignKey('app_users_resources.id')) + creator_id: Mapped[Optional[uuid.UUID]] = mapped_column(GUID, ForeignKey('app_users_app_users.id')) + type: Mapped[Optional[str]] = mapped_column(String) + + name: Mapped[Optional[str]] = mapped_column(String) + date_created: Mapped[Optional[datetime]] = mapped_column(DateTime, default=dt.datetime.utcnow) + lock_when_finished: Mapped[bool] = mapped_column(Boolean, default=False) + _attributes: Mapped[Optional[str]] = mapped_column(String) + _user_lock: Mapped[Optional[str]] = mapped_column(String) + + resource: Mapped[Optional["Resource"]] = relationship( + 'Resource', + back_populates='workflows', + ) + creator: Mapped[Optional["AppUser"]] = relationship('AppUser', back_populates='workflows') + steps: Mapped[list["ResourceWorkflowStep"]] = relationship( + 'ResourceWorkflowStep', + order_by='ResourceWorkflowStep.order', + back_populates='workflow', + cascade='all,delete', + ) + results: Mapped[list["ResourceWorkflowResult"]] = relationship( + 'ResourceWorkflowResult', + order_by='ResourceWorkflowResult.order', + back_populates='workflow', + cascade='all,delete', + ) __mapper_args__ = { 'polymorphic_on': 'type', diff --git a/tethysext/atcore/models/app_users/resource_workflow_result.py b/tethysext/atcore/models/app_users/resource_workflow_result.py index 68a06f11..5a4a88a3 100644 --- a/tethysext/atcore/models/app_users/resource_workflow_result.py +++ b/tethysext/atcore/models/app_users/resource_workflow_result.py @@ -7,14 +7,20 @@ ******************************************************************************** """ import uuid +from typing import TYPE_CHECKING, Optional + from sqlalchemy.orm import Session -from sqlalchemy.orm import relationship, backref -from sqlalchemy import Column, ForeignKey, String, PickleType, Integer +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import ForeignKey, String, PickleType, Integer from tethysext.atcore.models.types import GUID from tethysext.atcore.mixins import StatusMixin, AttributesMixin, OptionsMixin, SerializeMixin from tethysext.atcore.models.app_users.base import AppUsersBase from tethysext.atcore.models.controller_metadata import ControllerMetadata +if TYPE_CHECKING: + from tethysext.atcore.models.app_users.resource_workflow import ResourceWorkflow + from tethysext.atcore.models.resource_workflow_steps.results_rws import ResultsResourceWorkflowStep + __all__ = ['ResourceWorkflowResult'] @@ -27,25 +33,40 @@ class ResourceWorkflowResult(AppUsersBase, StatusMixin, AttributesMixin, Options CONTROLLER = 'tethysext.atcore.controllers.resource_workflows.workflow_results_view.WorkflowResultsView' TYPE = 'generic_workflow_result' - id = Column(GUID, primary_key=True, default=uuid.uuid4) - resource_workflow_id = Column(GUID, ForeignKey('app_users_resource_workflows.id')) - controller_metadata_id = Column(GUID, ForeignKey('app_users_controller_metadata.id')) - type = Column(String) - name = Column(String) - codename = Column(String) - description = Column(String) - order = Column(Integer) - _data = Column(PickleType, default={}) - _options = Column(PickleType, default={}) - _attributes = Column(String) - - _controller = relationship( + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + resource_workflow_id: Mapped[Optional[uuid.UUID]] = mapped_column( + GUID, ForeignKey('app_users_resource_workflows.id'), + ) + controller_metadata_id: Mapped[Optional[uuid.UUID]] = mapped_column( + GUID, ForeignKey('app_users_controller_metadata.id'), + ) + type: Mapped[Optional[str]] = mapped_column(String) + name: Mapped[Optional[str]] = mapped_column(String) + codename: Mapped[Optional[str]] = mapped_column(String) + description: Mapped[Optional[str]] = mapped_column(String) + order: Mapped[Optional[int]] = mapped_column(Integer) + _data: Mapped[Optional[dict]] = mapped_column(PickleType, default={}) + _options: Mapped[Optional[dict]] = mapped_column(PickleType, default={}) + _attributes: Mapped[Optional[str]] = mapped_column(String) + + _controller: Mapped[Optional["ControllerMetadata"]] = relationship( 'ControllerMetadata', - backref=backref('result'), + back_populates='result', cascade='all,delete', uselist=False, ) + workflow: Mapped[Optional["ResourceWorkflow"]] = relationship( + 'ResourceWorkflow', + back_populates='results', + ) + + steps: Mapped[list["ResultsResourceWorkflowStep"]] = relationship( + 'ResultsResourceWorkflowStep', + secondary='app_users_step_result_association', + back_populates='results', + ) + __mapper_args__ = { 'polymorphic_on': 'type', 'polymorphic_identity': TYPE diff --git a/tethysext/atcore/models/app_users/resource_workflow_step.py b/tethysext/atcore/models/app_users/resource_workflow_step.py index 3df41f55..acec57fd 100644 --- a/tethysext/atcore/models/app_users/resource_workflow_step.py +++ b/tethysext/atcore/models/app_users/resource_workflow_step.py @@ -10,9 +10,10 @@ import uuid from abc import abstractmethod from copy import deepcopy +from typing import TYPE_CHECKING, Optional -from sqlalchemy import Column, ForeignKey, String, PickleType, Integer, Boolean -from sqlalchemy.orm import relationship, backref +from sqlalchemy import ForeignKey, String, PickleType, Integer, Boolean +from sqlalchemy.orm import Mapped, mapped_column, relationship from tethysext.atcore.models.types import GUID from tethysext.atcore.mixins import StatusMixin, AttributesMixin, OptionsMixin from tethysext.atcore.models.app_users.base import AppUsersBase @@ -20,6 +21,10 @@ from tethysext.atcore.models.controller_metadata import ControllerMetadata from tethysext.atcore.utilities import json_serializer +if TYPE_CHECKING: + from tethysext.atcore.models.app_users.resource_workflow import ResourceWorkflow + from tethysext.atcore.models.resource_workflow_steps.results_rws import ResultsResourceWorkflowStep + __all__ = ['ResourceWorkflowStep'] @@ -57,44 +62,61 @@ class ResourceWorkflowStep(AppUsersBase, StatusMixin, AttributesMixin, OptionsMi UUID_FIELDS = ['id', 'child_id', 'resource_workflow_id'] SERIALIZED_FIELDS = ['id', 'child_id', 'resource_workflow_id', 'type', 'name', 'help'] - id = Column(GUID, primary_key=True, default=uuid.uuid4) - result_id = Column(GUID, ForeignKey('app_users_resource_workflow_steps.id')) - controller_metadata_id = Column(GUID, ForeignKey('app_users_controller_metadata.id')) - resource_workflow_id = Column(GUID, ForeignKey('app_users_resource_workflows.id')) - type = Column(String) - - name = Column(String) - help = Column(String) - order = Column(Integer) - status = Column(String) - dirty = Column(Boolean, default=False) - _options = Column(PickleType, default={}) - _attributes = Column(String) - _parameters = Column(PickleType, default={}) - _active_roles = Column(PickleType, default=[]) - - _controller = relationship( + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + result_id: Mapped[Optional[uuid.UUID]] = mapped_column(GUID, ForeignKey('app_users_resource_workflow_steps.id')) + controller_metadata_id: Mapped[Optional[uuid.UUID]] = mapped_column( + GUID, ForeignKey('app_users_controller_metadata.id'), + ) + resource_workflow_id: Mapped[Optional[uuid.UUID]] = mapped_column( + GUID, ForeignKey('app_users_resource_workflows.id'), + ) + type: Mapped[Optional[str]] = mapped_column(String) + + name: Mapped[Optional[str]] = mapped_column(String) + help: Mapped[Optional[str]] = mapped_column(String) + order: Mapped[Optional[int]] = mapped_column(Integer) + status: Mapped[Optional[str]] = mapped_column(String) + dirty: Mapped[bool] = mapped_column(Boolean, default=False) + _options: Mapped[Optional[dict]] = mapped_column(PickleType, default={}) + _attributes: Mapped[Optional[str]] = mapped_column(String) + _parameters: Mapped[Optional[dict]] = mapped_column(PickleType, default={}) + _active_roles: Mapped[Optional[list]] = mapped_column(PickleType, default=[]) + + _controller: Mapped[Optional["ControllerMetadata"]] = relationship( 'ControllerMetadata', - backref='step', + back_populates='step', cascade='all,delete', uselist=False, ) - children = relationship( + children: Mapped[list["ResourceWorkflowStep"]] = relationship( 'ResourceWorkflowStep', secondary=step_parent_child_association, - backref=backref('parents'), + back_populates='parents', secondaryjoin=id == step_parent_child_association.c.child_id, primaryjoin=id == step_parent_child_association.c.parent_id, cascade='all,delete', ) - result = relationship( + parents: Mapped[list["ResourceWorkflowStep"]] = relationship( + 'ResourceWorkflowStep', + secondary=step_parent_child_association, + back_populates='children', + secondaryjoin=id == step_parent_child_association.c.parent_id, + primaryjoin=id == step_parent_child_association.c.child_id, + ) + + result: Mapped[Optional["ResultsResourceWorkflowStep"]] = relationship( 'ResultsResourceWorkflowStep', - backref=backref('source', uselist=False), + back_populates='source', foreign_keys=[result_id], remote_side=[id], - cascade='all,delete' + cascade='all,delete', + ) + + workflow: Mapped[Optional["ResourceWorkflow"]] = relationship( + 'ResourceWorkflow', + back_populates='steps', ) __mapper_args__ = { diff --git a/tethysext/atcore/models/app_users/spatial_resource.py b/tethysext/atcore/models/app_users/spatial_resource.py index eb8c8e5d..ab875c4f 100644 --- a/tethysext/atcore/models/app_users/spatial_resource.py +++ b/tethysext/atcore/models/app_users/spatial_resource.py @@ -7,10 +7,11 @@ ******************************************************************************** """ import json -from typing import Union +from typing import Optional, Union -from sqlalchemy import func, inspect, Column -from geoalchemy2.types import Geometry +from sqlalchemy import func, inspect, select +from sqlalchemy.orm import Mapped, mapped_column +from geoalchemy2.types import Geometry, WKBElement from tethysext.atcore.exceptions import InvalidSpatialResourceExtentTypeError from tethysext.atcore.models.app_users.resource import Resource @@ -24,7 +25,7 @@ class SpatialResource(Resource): DISPLAY_TYPE_SINGULAR = 'Spatial Resource' DISPLAY_TYPE_PLURAL = 'Spatial Resources' - extent = Column(Geometry) + extent: Mapped[Optional[WKBElement]] = mapped_column(Geometry) # Polymorphism __mapper_args__ = { @@ -48,11 +49,11 @@ def set_extent(self, obj: Union[dict, str], object_format: str = 'dict', srid=43 object_to_convert = json.dumps(obj) session = inspect(self).session if object_format == 'wkt': - qry = session.query(func.ST_SetSRID(func.ST_GeomFromEWKT(object_to_convert), srid).label('geom')) - new_extent = qry.first().geom + stmt = select(func.ST_SetSRID(func.ST_GeomFromEWKT(object_to_convert), srid).label('geom')) + new_extent = session.execute(stmt).first().geom else: - qry = session.query(func.ST_SetSRID(func.ST_GeomFromGeoJSON(object_to_convert), srid).label('geom')) - new_extent = qry.first().geom + stmt = select(func.ST_SetSRID(func.ST_GeomFromGeoJSON(object_to_convert), srid).label('geom')) + new_extent = session.execute(stmt).first().geom self.extent = new_extent def get_extent(self, extent_type: str = 'dict'): @@ -71,17 +72,17 @@ def get_extent(self, extent_type: str = 'dict'): session = inspect(self).session if extent_type == 'wkt': - qry = session.query(func.ST_AsEWKT(self.extent).label('extent')) + stmt = select(func.ST_AsEWKT(self.extent).label('extent')) else: - qry = session.query(func.ST_AsGeoJSON(self.extent).label('extent')) - extent = qry.first().extent + stmt = select(func.ST_AsGeoJSON(self.extent).label('extent')) + extent = session.execute(stmt).first().extent if extent_type == 'dict': extent = json.loads(extent) return extent def update_extent_srid(self, srid): session = inspect(self).session - qry = session.query(func.ST_SetSRID(self.extent, srid).label('geom')) - new_extent = qry.first().geom + stmt = select(func.ST_SetSRID(self.extent, srid).label('geom')) + new_extent = session.execute(stmt).first().geom self.extent = new_extent diff --git a/tethysext/atcore/models/app_users/user_setting.py b/tethysext/atcore/models/app_users/user_setting.py index 4cb2660e..1a95e0b3 100644 --- a/tethysext/atcore/models/app_users/user_setting.py +++ b/tethysext/atcore/models/app_users/user_setting.py @@ -8,12 +8,16 @@ """ import uuid import json -from sqlalchemy import Column, ForeignKey, String +from typing import TYPE_CHECKING, Optional +from sqlalchemy import ForeignKey, String from tethysext.atcore.models.types.guid import GUID -from sqlalchemy.orm import relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship from .base import AppUsersBase from tethysext.atcore.mixins import AttributesMixin +if TYPE_CHECKING: + from .app_user import AppUser + class UserSetting(AttributesMixin, AppUsersBase): """ @@ -22,13 +26,13 @@ class UserSetting(AttributesMixin, AppUsersBase): __tablename__ = "app_users_user_settings" # Primary and Foreign Keys - id = Column(GUID, primary_key=True, default=uuid.uuid4) - user_id = Column(GUID, ForeignKey('app_users_app_users.id')) + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + user_id: Mapped[Optional[uuid.UUID]] = mapped_column(GUID, ForeignKey('app_users_app_users.id')) # Properties - _attributes = Column(String, default=json.dumps({})) - key = Column(String) - value = Column(String) + _attributes: Mapped[Optional[str]] = mapped_column(String, default=json.dumps({})) + key: Mapped[Optional[str]] = mapped_column(String) + value: Mapped[Optional[str]] = mapped_column(String) # Relationship - user = relationship('AppUser', back_populates='settings') + user: Mapped[Optional["AppUser"]] = relationship('AppUser', back_populates='settings') diff --git a/tethysext/atcore/models/controller_metadata.py b/tethysext/atcore/models/controller_metadata.py index 7f5007f2..39bc69fe 100644 --- a/tethysext/atcore/models/controller_metadata.py +++ b/tethysext/atcore/models/controller_metadata.py @@ -8,12 +8,18 @@ """ import inspect import uuid +from typing import TYPE_CHECKING, Optional -from sqlalchemy import Column, String, PickleType +from sqlalchemy import String, PickleType +from sqlalchemy.orm import Mapped, mapped_column, relationship from tethysext.atcore.models.types import GUID from tethysext.atcore.models.app_users.base import AppUsersBase from tethysext.atcore.utilities import import_from_string +if TYPE_CHECKING: + from tethysext.atcore.models.app_users.resource_workflow_step import ResourceWorkflowStep + from tethysext.atcore.models.app_users.resource_workflow_result import ResourceWorkflowResult + __all__ = ['ControllerMetadata'] @@ -23,10 +29,22 @@ class ControllerMetadata(AppUsersBase): """ __tablename__ = 'app_users_controller_metadata' - id = Column(GUID, primary_key=True, default=uuid.uuid4) - path = Column(String) - kwargs = Column(PickleType, default={}) - http_methods = Column(PickleType, default=['get', 'post', 'delete']) + id: Mapped[uuid.UUID] = mapped_column(GUID, primary_key=True, default=uuid.uuid4) + path: Mapped[Optional[str]] = mapped_column(String) + kwargs: Mapped[Optional[dict]] = mapped_column(PickleType, default={}) + http_methods: Mapped[Optional[list]] = mapped_column(PickleType, default=['get', 'post', 'delete']) + + step: Mapped[Optional["ResourceWorkflowStep"]] = relationship( + 'ResourceWorkflowStep', + back_populates='_controller', + uselist=False, + ) + + result: Mapped[Optional["ResourceWorkflowResult"]] = relationship( + 'ResourceWorkflowResult', + back_populates='_controller', + uselist=False, + ) def instantiate(self, **kwargs): """ diff --git a/tethysext/atcore/models/file_database/file_collection.py b/tethysext/atcore/models/file_database/file_collection.py index 2e27a24c..8061a7f9 100644 --- a/tethysext/atcore/models/file_database/file_collection.py +++ b/tethysext/atcore/models/file_database/file_collection.py @@ -7,21 +7,27 @@ ******************************************************************************** """ import uuid +from typing import TYPE_CHECKING, Optional -from sqlalchemy import Column, ForeignKey +from sqlalchemy import ForeignKey from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy.orm import relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship from tethysext.atcore.models.app_users.base import AppUsersBase from tethysext.atcore.models.types import GUID +if TYPE_CHECKING: + from tethysext.atcore.models.file_database.file_database import FileDatabase + class FileCollection(AppUsersBase): """A model representing a FileCollection""" __tablename__ = "file_collections" - id = Column('id', GUID, primary_key=True, default=uuid.uuid4) - file_database_id = Column('file_database_id', GUID, ForeignKey('file_databases.id')) - meta = Column('metadata', JSON) + id: Mapped[uuid.UUID] = mapped_column('id', GUID, primary_key=True, default=uuid.uuid4) + file_database_id: Mapped[Optional[uuid.UUID]] = mapped_column( + 'file_database_id', GUID, ForeignKey('file_databases.id'), + ) + meta: Mapped[Optional[dict]] = mapped_column('metadata', JSON) - database = relationship("FileDatabase", back_populates="collections") + database: Mapped[Optional["FileDatabase"]] = relationship("FileDatabase", back_populates="collections") diff --git a/tethysext/atcore/models/file_database/file_database.py b/tethysext/atcore/models/file_database/file_database.py index 2d597682..b01d5230 100644 --- a/tethysext/atcore/models/file_database/file_database.py +++ b/tethysext/atcore/models/file_database/file_database.py @@ -7,12 +7,13 @@ ******************************************************************************** """ import uuid +from typing import Optional -from sqlalchemy import Column from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy.orm import relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship from tethysext.atcore.models.app_users.base import AppUsersBase +from tethysext.atcore.models.file_database.file_collection import FileCollection from tethysext.atcore.models.types import GUID @@ -20,7 +21,7 @@ class FileDatabase(AppUsersBase): """A model representing a FileDatabase""" __tablename__ = "file_databases" - id = Column('id', GUID, primary_key=True, default=uuid.uuid4) - meta = Column('metadata', JSON) + id: Mapped[uuid.UUID] = mapped_column('id', GUID, primary_key=True, default=uuid.uuid4) + meta: Mapped[Optional[dict]] = mapped_column('metadata', JSON) - collections = relationship("FileCollection", back_populates="database") + collections: Mapped[list[FileCollection]] = relationship("FileCollection", back_populates="database") diff --git a/tethysext/atcore/models/resource_workflow_results/spatial_workflow_result.py b/tethysext/atcore/models/resource_workflow_results/spatial_workflow_result.py index 56bc7cd7..19032e7e 100644 --- a/tethysext/atcore/models/resource_workflow_results/spatial_workflow_result.py +++ b/tethysext/atcore/models/resource_workflow_results/spatial_workflow_result.py @@ -108,7 +108,7 @@ def add_geojson_layer(self, geojson, layer_name, layer_title, layer_variable, la Args: geojson(dict): Python equivalent GeoJSON FeatureCollection. - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. @@ -154,7 +154,7 @@ def add_wms_layer(self, endpoint, layer_name, layer_title, layer_variable, layer Args: endpoint(str): URL to GeoServer WMS interface. - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. @@ -219,7 +219,7 @@ def add_cesium_layer(self, cesium_type, cesium_json, layer_name, layer_title, la Args: cesium_type(enum): 'CesiumModel' or 'CesiumPrimitive' cesium_json(dict): Cesium object in json. - layer_name(str): Name of cesium layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of cesium layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. diff --git a/tethysext/atcore/models/resource_workflow_steps/results_rws.py b/tethysext/atcore/models/resource_workflow_steps/results_rws.py index d85fcb7a..1e35e152 100644 --- a/tethysext/atcore/models/resource_workflow_steps/results_rws.py +++ b/tethysext/atcore/models/resource_workflow_steps/results_rws.py @@ -6,11 +6,16 @@ * Copyright: (c) Aquaveo 2019 ******************************************************************************** """ -from sqlalchemy.orm import relationship +from typing import TYPE_CHECKING, Optional + +from sqlalchemy.orm import Mapped, relationship from tethysext.atcore.mixins import ResultsMixin, AttributesMixin from tethysext.atcore.models.app_users import ResourceWorkflowStep from tethysext.atcore.models.app_users.associations import step_result_association +if TYPE_CHECKING: + from tethysext.atcore.models.app_users.resource_workflow_result import ResourceWorkflowResult + class ResultsResourceWorkflowStep(ResourceWorkflowStep, AttributesMixin, ResultsMixin): """ @@ -22,12 +27,19 @@ class ResultsResourceWorkflowStep(ResourceWorkflowStep, AttributesMixin, Results 'polymorphic_identity': TYPE } - results = relationship( + results: Mapped[list["ResourceWorkflowResult"]] = relationship( 'ResourceWorkflowResult', secondary=step_result_association, order_by='ResourceWorkflowResult.order', cascade='all,delete', - backref='steps' + back_populates='steps', + ) + + source: Mapped[Optional["ResourceWorkflowStep"]] = relationship( + 'ResourceWorkflowStep', + back_populates='result', + uselist=False, + foreign_keys='ResourceWorkflowStep.result_id', ) @property diff --git a/tethysext/atcore/models/types/guid.py b/tethysext/atcore/models/types/guid.py index 8cf1c4be..8812b883 100644 --- a/tethysext/atcore/models/types/guid.py +++ b/tethysext/atcore/models/types/guid.py @@ -34,5 +34,7 @@ def process_bind_param(self, value, dialect): def process_result_value(self, value, dialect): if value is None: return value + elif isinstance(value, uuid.UUID): + return value else: return uuid.UUID(value) diff --git a/tethysext/atcore/services/app_users/decorators.py b/tethysext/atcore/services/app_users/decorators.py index 1d91f709..ad7e98f4 100644 --- a/tethysext/atcore/services/app_users/decorators.py +++ b/tethysext/atcore/services/app_users/decorators.py @@ -8,6 +8,7 @@ """ import logging import traceback +from sqlalchemy import select from sqlalchemy.exc import StatementError from sqlalchemy.orm.exc import NoResultFound from django.http import JsonResponse @@ -41,9 +42,9 @@ def _wrapped_controller(self, request, *args, **kwargs): make_session = self.get_sessionmaker() session = make_session() - app_user = session.query(_AppUser).\ - filter(_AppUser.username == request.user.username).\ - one_or_none() + app_user = session.execute( + select(_AppUser).where(_AppUser.username == request.user.username) + ).scalar_one_or_none() session.close() if app_user is None: diff --git a/tethysext/atcore/services/file_database.py b/tethysext/atcore/services/file_database.py index fa9e42e4..2157be41 100644 --- a/tethysext/atcore/services/file_database.py +++ b/tethysext/atcore/services/file_database.py @@ -14,6 +14,7 @@ import uuid from typing import Generator +from sqlalchemy import func, select from sqlalchemy.orm import Session from tethysext.atcore.exceptions import FileCollectionNotFoundError, FileDatabaseNotFoundError, \ @@ -75,7 +76,7 @@ def instance(self) -> FileDatabase: if self.__deleted: raise UnboundFileDatabaseError('The file database has been deleted.') if not self._instance: - self._instance = self._session.query(FileDatabase).get(self._database_id) + self._instance = self._session.get(FileDatabase, self._database_id) if self._instance is None: raise FileDatabaseNotFoundError(f'FileDatabase with id "{str(self._database_id)}" not found.') return self._instance @@ -109,9 +110,12 @@ def get_collection(self, collection_id: uuid.UUID) -> 'FileCollectionClient': Returns: The FileCollectionClient for the FileCollection. """ - file_collection_count = self._session.query(FileCollection)\ - .filter_by(id=collection_id, file_database_id=self.instance.id)\ - .count() + file_collection_count = self._session.execute( + select(func.count()) + .select_from(FileCollection) + .where(FileCollection.id == collection_id) + .where(FileCollection.file_database_id == self.instance.id) + ).scalar() if file_collection_count != 1: raise FileCollectionNotFoundError(f'Collection with id "{str(collection_id)}" could not ' f'be found with this database.') @@ -239,7 +243,7 @@ def instance(self) -> FileCollection: if self.__deleted: raise UnboundFileCollectionError('The collection has been deleted.') if not self._instance: - self._instance = self._session.query(FileCollection).get(self._collection_id) + self._instance = self._session.get(FileCollection, self._collection_id) if self._instance is None: raise FileCollectionNotFoundError(f'FileCollection with id "{str(self._collection_id)}" not found.') return self._instance diff --git a/tethysext/atcore/services/map_manager.py b/tethysext/atcore/services/map_manager.py index f4683500..f9a8e817 100644 --- a/tethysext/atcore/services/map_manager.py +++ b/tethysext/atcore/services/map_manager.py @@ -95,12 +95,28 @@ def default_view(self): @abstractmethod def compose_map(self, request, *args, **kwargs): """ - Compose the MapView object. + Compose the MapView object, its default extent, and any layer groups. + + The MapView controller calls this method and unpacks the result as + ``map_view, model_extent, layer_groups = map_manager.compose_map(...)``, + so subclass implementations must return all three values. + Args: request(HttpRequest): A Django request object. Returns: - MapView, 4-list<float>: The MapView and extent objects. + tuple: A 3-tuple of ``(MapView, 4-list<float>, list<dict>)``: + + - **MapView** — the configured Tethys ``MapView`` gizmo. + - **4-list<float>** — the default map extent ``[minx, miny, maxx, maxy]``. + - **list<dict>** — layer groups built via :meth:`build_layer_group`. + May be empty. + + Notes: + The ``MapView`` controller overwrites ``controls``, ``legend``, + ``height``, ``width``, ``feature_selection``, and ``disable_basemap`` + on the returned ``MapView`` after this method runs, so setting + those fields here has no effect. """ def get_cesium_token(self): @@ -139,7 +155,7 @@ def build_geojson_layer(self, geojson, layer_name, layer_title, layer_variable, Build an MVLayer object with supplied arguments. Args: geojson(dict): Python equivalent GeoJSON FeatureCollection. - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. @@ -195,7 +211,7 @@ def build_cesium_layer(self, cesium_type, cesium_json, layer_name, layer_title, Args: cesium_type(enum): 'CesiumModel' or 'CesiumPrimitive'. cesium_json(dict): Cesium dictionary to describe the layer. - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. @@ -248,7 +264,7 @@ def build_wms_layer(self, endpoint, layer_name, layer_title, layer_variable, sty Build an WMS MVLayer object with supplied arguments. Args: endpoint(str): URL to GeoServer WMS interface. - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). style(str): Name of the Geoserver layer style @@ -351,7 +367,7 @@ def build_arc_gis_layer(self, endpoint, layer_name, layer_title, layer_variable, Build an AcrGIS Map Server MVLayer object with supplied arguments. Args: endpoint(str): URL to GeoServer WMS interface. - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. @@ -408,7 +424,7 @@ def _build_mv_layer(self, layer_source, layer_name, layer_title, layer_variable, Build an MVLayer object with supplied arguments. Args: layer_source(str): OpenLayers Source to use for the MVLayer (e.g.: "TileWMS", "ImageWMS", "GeoJSON"). - layer_name(str): Name of GeoServer layer (e.g.: agwa:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). layer_title(str): Title of MVLayer (e.g.: Model Boundaries). layer_variable(str): Variable type of the layer (e.g.: model_boundaries). layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. diff --git a/tethysext/atcore/services/model_database.py b/tethysext/atcore/services/model_database.py index 907db63d..7353485b 100644 --- a/tethysext/atcore/services/model_database.py +++ b/tethysext/atcore/services/model_database.py @@ -8,6 +8,8 @@ """ import operator +from sqlalchemy import text + from tethysext.atcore.services.model_database_connection import ModelDatabaseConnection from tethysext.atcore.services.model_database_base import ModelDatabaseBase @@ -50,15 +52,16 @@ def get_size(self, pretty=False): AND relname SIMILAR TO '%%epanet_\w+%%'; '''.format(selection=selection) - result = engine.execute(query) + try: + with engine.connect() as connection: + result = connection.execute(text(query)) - if not pretty: - size = result.scalar() - else: - size = result.fetchone().size - - result.close() - engine.dispose() + if not pretty: + size = result.scalar() + else: + size = result.fetchone().size + finally: + engine.dispose() return size @@ -174,29 +177,31 @@ def _get_cluster_connection_name_for_new_database(self): if not curr_engine: continue - # Get count of all databases: SELECT count(*) FROM pg_database; - response = curr_engine.execute( - 'SELECT count(*) AS count ' - 'FROM pg_database;' - ) - - for row in response: - count = row.count - - # Get total cluster size (pretty): SELECT pg_catalog.pg_size_pretty(sum(pg_catalog.pg_database_size(d.datname))) AS Size FROM pg_catalog.pg_database d # noqa: E501 - # Get total cluster size (bytes): SELECT sum(pg_catalog.pg_database_size(d.datname)) AS Size FROM pg_catalog.pg_database d # noqa: E501 - response = curr_engine.execute( - 'SELECT sum(pg_catalog.pg_database_size(d.datname)) AS size ' - 'FROM pg_catalog.pg_database d;' - ) - - for row in response: - size_bytes = row.size + try: + with curr_engine.connect() as connection: + # Get count of all databases: SELECT count(*) FROM pg_database; + response = connection.execute(text( + 'SELECT count(*) AS count ' + 'FROM pg_database;' + )) + + for row in response: + count = row.count + + # Get total cluster size (pretty): SELECT pg_catalog.pg_size_pretty(sum(pg_catalog.pg_database_size(d.datname))) AS Size FROM pg_catalog.pg_database d # noqa: E501 + # Get total cluster size (bytes): SELECT sum(pg_catalog.pg_database_size(d.datname)) AS Size FROM pg_catalog.pg_database d # noqa: E501 + response = connection.execute(text( + 'SELECT sum(pg_catalog.pg_database_size(d.datname)) AS size ' + 'FROM pg_catalog.pg_database d;' + )) + + for row in response: + size_bytes = row.size + finally: + curr_engine.dispose() db_stats.append((connection_name, count, size_bytes)) - curr_engine.dispose() - # Logic for which connection here if not db_stats: return None diff --git a/tethysext/atcore/services/model_db_spatial_manager.py b/tethysext/atcore/services/model_db_spatial_manager.py index 071d8c13..7c379198 100644 --- a/tethysext/atcore/services/model_db_spatial_manager.py +++ b/tethysext/atcore/services/model_db_spatial_manager.py @@ -8,6 +8,9 @@ ******************************************************************************** """ from abc import abstractmethod + +from sqlalchemy import text + from tethysext.atcore.services.exceptions import UnitsNotFound, UnknownUnits from tethysext.atcore.services.base_spatial_manager import BaseSpatialManager @@ -42,15 +45,16 @@ def get_projection_units(self, model_db, srid): db_engine = model_db.get_engine() try: sql = "SELECT srid, proj4text FROM spatial_ref_sys WHERE srid = {}".format(srid) - ret = db_engine.execute(sql) + with db_engine.connect() as connection: + ret = connection.execute(text(sql)) - # Parse proj4text to get units - # e.g.: +proj=utm +zone=21 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs - proj4text = '' - units = '' + # Parse proj4text to get units + # e.g.: +proj=utm +zone=21 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs + proj4text = '' + units = '' - for row in ret: - proj4text = row.proj4text + for row in ret: + proj4text = row.proj4text finally: db_engine.dispose() @@ -100,11 +104,12 @@ def get_projection_string(self, model_db, srid, proj_format=''): else: sql = "SELECT proj4text AS proj_string FROM spatial_ref_sys WHERE srid = {}".format(srid) - ret = db_engine.execute(sql) - projection_string = '' + with db_engine.connect() as connection: + ret = connection.execute(text(sql)) + projection_string = '' - for row in ret: - projection_string = row.proj_string + for row in ret: + projection_string = row.proj_string finally: db_engine.dispose() diff --git a/tethysext/atcore/services/resource_condor_workflow.py b/tethysext/atcore/services/resource_condor_workflow.py index 0ce809c2..be8934e5 100644 --- a/tethysext/atcore/services/resource_condor_workflow.py +++ b/tethysext/atcore/services/resource_condor_workflow.py @@ -128,7 +128,7 @@ def run_job(self): resource_db_engine = create_engine(self.resource_db_url, **self.db_engine_kwargs) make_resource_db_session = sessionmaker(bind=resource_db_engine) resource_db_session = make_resource_db_session() - resource = resource_db_session.query(Resource).get(self.resource_id) + resource = resource_db_session.get(Resource, self.resource_id) resource.set_status(Resource.ROOT_STATUS_KEY, Resource.STATUS_PENDING) resource_db_session.commit() diff --git a/tethysext/atcore/services/resource_workflows/decorators.py b/tethysext/atcore/services/resource_workflows/decorators.py index 152fe47b..6407ed73 100644 --- a/tethysext/atcore/services/resource_workflows/decorators.py +++ b/tethysext/atcore/services/resource_workflows/decorators.py @@ -151,11 +151,11 @@ def _wrapped(): # errors with a subclass of the ResourceWorkflowStep. # It could also be caused indirectly if the subclass # has Pickle typed columns with values that import things. - step = resource_db_session.query(ResourceWorkflowStep).get(args.resource_workflow_step_id) + step = resource_db_session.get(ResourceWorkflowStep, args.resource_workflow_step_id) # IMPORTANT: External Resource classes need to be imported at the top of the job file to # allow sqlalchemy to resolve the polymorphic identity. - resource = resource_db_session.query(ResourceClass).get(args.resource_id) + resource = resource_db_session.get(ResourceClass, args.resource_id) # Process parameters from workflow steps with open(args.workflow_params_file, 'r') as p: diff --git a/tethysext/atcore/services/resource_workflows/helpers.py b/tethysext/atcore/services/resource_workflows/helpers.py index 5adb041b..06f886ed 100644 --- a/tethysext/atcore/services/resource_workflows/helpers.py +++ b/tethysext/atcore/services/resource_workflows/helpers.py @@ -29,7 +29,7 @@ def parse_workflow_step_args(): ) parser.add_argument( 'model_db_url', - help='SQLAlchemy URL to the database containing the GSSHA model.' + help='SQLAlchemy URL to the database containing the model data.' ) parser.add_argument( 'resource_id', @@ -66,14 +66,14 @@ def parse_workflow_step_args(): parser.add_argument( '-s', '--scenario_id', dest='scenario_id', - help='Scenario ID for this GSSHA model.', + help='Scenario ID for the model.', default=1 ) parser.add_argument( '-a', '--app_namespace', help='Namespace of the app the database belongs to.', dest='app_namespace', - default='agwa' + default='app' ) args, unknown_args = parser.parse_known_args() return args, unknown_args diff --git a/tethysext/atcore/services/spatial_reference.py b/tethysext/atcore/services/spatial_reference.py index a0f4a4af..9f08d69f 100644 --- a/tethysext/atcore/services/spatial_reference.py +++ b/tethysext/atcore/services/spatial_reference.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ +from sqlalchemy import text class SpatialReferenceService: @@ -38,17 +39,17 @@ def get_spatial_reference_system_by_srid(self, srid): # Retrieve a list of SRIDs from database get_spatial_ref_list = "SELECT * FROM spatial_ref_sys WHERE ({0} = @srid)".format(srid) - spatial_ref_object_result = self.db_engine.execute(get_spatial_ref_list) + with self.db_engine.connect() as connection: + spatial_ref_object_result = connection.execute(text(get_spatial_ref_list)) - # Parse out the wanted items into the list for the select input - for spatial_reference in spatial_ref_object_result: - spatial_ref_list.append( - { - "text": "{0} {1}".format(spatial_reference[0], spatial_reference[3].split('"')[1]), - "id": str(spatial_reference[0]) - } - ) - spatial_ref_object_result.close() + # Parse out the wanted items into the list for the select input + for spatial_reference in spatial_ref_object_result: + spatial_ref_list.append( + { + "text": "{0} {1}".format(spatial_reference[0], spatial_reference[3].split('"')[1]), + "id": str(spatial_reference[0]) + } + ) json = {'results': spatial_ref_list} return json @@ -72,12 +73,12 @@ def get_wkt_by_srid(self, srid): # Retrieve a list of SRIDs from database get_spatial_ref_list = "SELECT srtext FROM spatial_ref_sys WHERE ({0} = @srid)".format(srid) - spatial_ref_object_result = self.db_engine.execute(get_spatial_ref_list) + with self.db_engine.connect() as connection: + spatial_ref_object_result = connection.execute(text(get_spatial_ref_list)) - # Get the WKT - for spatial_reference in spatial_ref_object_result: - wkt = spatial_reference[0] - spatial_ref_object_result.close() + # Get the WKT + for spatial_reference in spatial_ref_object_result: + wkt = spatial_reference[0] json = {'results': wkt} return json @@ -100,17 +101,17 @@ def get_spatial_reference_system_by_query_string(self, query_words): "WHERE to_tsvector('english', srtext) @@ " \ "to_tsquery('english', '{0}');".format(sql_query_input) - spatial_ref_object_result = self.db_engine.execute(get_spatial_ref_list) - - # Parse out the wanted items into the list for the select input - for spatial_reference in spatial_ref_object_result: - spatial_ref_list.append( - { - "text": "{0} {1}".format(spatial_reference[0], spatial_reference[3].split('"')[1]), - "id": str(spatial_reference[0]) - } - ) - spatial_ref_object_result.close() + with self.db_engine.connect() as connection: + spatial_ref_object_result = connection.execute(text(get_spatial_ref_list)) + + # Parse out the wanted items into the list for the select input + for spatial_reference in spatial_ref_object_result: + spatial_ref_list.append( + { + "text": "{0} {1}".format(spatial_reference[0], spatial_reference[3].split('"')[1]), + "id": str(spatial_reference[0]) + } + ) json = {'results': spatial_ref_list} diff --git a/tethysext/atcore/services/workflow_manager/condor_workflow_manager.py b/tethysext/atcore/services/workflow_manager/condor_workflow_manager.py index e1ac97f1..fb88c8bf 100644 --- a/tethysext/atcore/services/workflow_manager/condor_workflow_manager.py +++ b/tethysext/atcore/services/workflow_manager/condor_workflow_manager.py @@ -336,7 +336,7 @@ def validate_jobs(self, jobs): 1. Jobs must be defined (not None or empty) 2. Jobs must be either: - a function, or - - a list of CondorWorkflowJobNode objects, equivalent dicaiontry, or a mix of both + - a list of CondorWorkflowJobNode objects, equivalent dictionary, or a mix of both Args: jobs(function | list<CondorWorkflowJobNode or dict>): The jobs to validate. diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/add_existing_user.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/add_existing_user.py index 7d552bb8..3a2fb25c 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/add_existing_user.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/add_existing_user.py @@ -164,6 +164,7 @@ def test__handle_modify_user_requests_permission_manager(self, mock_get_app_user self.assertEqual('NameSpace:app_users_manage_users', mock_reverse.call_args_list[0][0][0]) + @mock.patch('tethysext.atcore.controllers.app_users.add_existing_user.select') @mock.patch('tethysext.atcore.controllers.app_users.add_existing_user.render') @mock.patch.object(AppUsersViewMixin, 'get_permissions_manager') @mock.patch('tethysext.atcore.controllers.app_users.add_existing_user.get_active_app') @@ -172,7 +173,7 @@ def test__handle_modify_user_requests_permission_manager(self, mock_get_app_user @mock.patch.object(AppUsersViewMixin, 'get_app_user_model') def test__handle_modify_user_requests_must_assign_user(self, mock_get_app_usermodel, _, mock_get_session_maker, mock_get_active_app, - __, mock_render): + __, mock_render, mock_select): session = mock_get_session_maker()() mock_dict = {'add-existing-user-submit': 'add-existing-user-submit', 'assign-role': 'role1', @@ -225,6 +226,7 @@ def test__handle_modify_user_requests_must_assign_user(self, mock_get_app_usermo self.assertEqual('Must assign user to at least one organization', mock_render.call_args_list[0][0][2]['organization_select']['error']) + @mock.patch('tethysext.atcore.controllers.app_users.add_existing_user.select') @mock.patch('tethysext.atcore.controllers.app_users.add_existing_user.render') @mock.patch('tethysext.atcore.controllers.app_users.add_existing_user.get_active_app') @mock.patch.object(AppUsersViewMixin, 'get_sessionmaker') @@ -232,7 +234,7 @@ def test__handle_modify_user_requests_must_assign_user(self, mock_get_app_usermo @mock.patch.object(AppUsersViewMixin, 'get_app_user_model') def test__handle_modify_user_requests_with_invalid_post_data(self, mock_get_app_usermodel, _, mock_get_session_maker, mock_get_active_app, - mock_render): + mock_render, mock_select): session = mock_get_session_maker()() mock_dict = {'add-existing-user-submit': 'add-existing-user-submit', 'assign-role': '', diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organization_members.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organization_members.py index 9057e84c..ffe51a05 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organization_members.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organization_members.py @@ -126,7 +126,7 @@ def test_handle_manage_member_request_get(self, _, mock_get_user_model, __, session = make_session() - session.query().get.return_value = self.organization + session.get.return_value = self.organization mock_request_app_user = mock.MagicMock() app_user.get_app_user_from_request.return_value = mock_request_app_user @@ -178,7 +178,7 @@ def test_handle_manage_member_request_post_remove_orphan_member(self, _, mock_ge make_session = mock_get_sessionmaker() session = make_session() - session.query().get.side_effect = [self.organization, self.app_user] + session.get.side_effect = [self.organization, self.app_user] app_user.get_app_user_from_request.return_value = self.app_user @@ -227,7 +227,7 @@ def test_handle_manage_member_request_post_is_client_remove(self, _, mock_get_ap app_user.get_app_user_from_request.return_value = self.app_user - session.query().get.return_value = self.organization + session.get.return_value = self.organization app_user.ROLES.get_no_organization_roles.return_value = ['Role1'] diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organizations.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organizations.py index 7559dfb7..b8d39f80 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organizations.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_organizations.py @@ -96,6 +96,7 @@ def test_not_delete(self): self.assertIn('"success": false', ret.content.decode("utf-8")) self.assertIn('"error": "Invalid action: foo"', ret.content.decode("utf-8")) + @mock.patch('tethysext.atcore.controllers.app_users.manage_organizations.select') @mock.patch('tethysext.atcore.controllers.app_users.manage_organizations.render') @mock.patch.object(ManageOrganizations, 'add_custom_fields') @mock.patch('tethysext.atcore.controllers.app_users.manage_organizations.has_permission') @@ -104,7 +105,7 @@ def test_not_delete(self): @mock.patch.object(AppUsersViewMixin, 'get_app_user_model') @mock.patch.object(AppUsersViewMixin, 'get_organization_model') def test_handle_get(self, _, mock_get_app_user, mock_get_session, __, mock_has_permissions, - ___, mock_render): + ___, mock_render, mock_select): mock_request = self.request_factory.get('/foo/bar/') mock_request.user = self.django_user @@ -113,7 +114,7 @@ def test_handle_get(self, _, mock_get_app_user, mock_get_session, __, mock_has_p mock_get_app_user().get_app_user_from_request.return_value = mock_app_user - mock_make_session.query().all.return_value = [self.organization] + mock_make_session.execute().scalars().all.return_value = [self.organization] mock_app_user.is_staff.return_value = True mock_has_permissions.return_value = True @@ -177,7 +178,7 @@ def test_handle_delete(self, _, mock_get_app_user, mock_session, mock_perform_de mock_get_app_user().get_app_user_from_request.return_value = mock_app_user - mock_make_session.query().get.return_value = self.organization + mock_make_session.get.return_value = self.organization # call the method organization_id = 'O001' @@ -213,7 +214,7 @@ def test_handle_delete_can_delete_organization(self, _, mock_get_app_user, mock_ app_user.get_app_user_from_request.return_value = mock_app_user self.organization.consultant = consultant - mock_make_session.query().get.return_value = self.organization + mock_make_session.get.return_value = self.organization # call the method organization_id = 'O001' @@ -243,7 +244,7 @@ def test_handle_delete_http_response_forbidden(self, _, mock_get_app_user, mock_ mock_get_app_user().get_app_user_from_request.return_value = mock_app_user - mock_make_session.query().get.return_value = self.organization + mock_make_session.get.return_value = self.organization mock_has_permission.return_value = False diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_resources.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_resources.py index 73659f16..d17b2bc4 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_resources.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_resources.py @@ -281,7 +281,7 @@ def test_handle_get_first_time(self, _, mock_app_user, __, mock_session_maker, m def test_handle_delete(self, _, mock_get_session, mock_custom_delete, __): session = mock_get_session()() mock_resource = mock.MagicMock() - session.query().get.return_value = mock_resource + session.get.return_value = mock_resource mock_request = self.request_factory.get('/foo/bar/') mock_request.user = self.django_user @@ -303,7 +303,7 @@ def test_handle_delete(self, _, mock_get_session, mock_custom_delete, __): @mock.patch.object(ResourceViewMixin, 'get_resource_model') def test_handle_delete_query_exception(self, _, mock_get_session, mock_custom_delete, __): session = mock_get_session()() - session.query().get.side_effect = Exception + session.get.side_effect = Exception mock_request = self.request_factory.get('/foo/bar/') mock_request.user = self.django_user diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_users_tests.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_users_tests.py index bb1381fd..060bd54d 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_users_tests.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/manage_users_tests.py @@ -200,7 +200,7 @@ def test_delete_delete(self): def test_delete_delete_exception(self): self.request.GET = {'action': 'delete', 'id': 123456} self.request.method = 'delete' - self.mock_get_session()().query.side_effect = Exception('Some exception message') + self.mock_get_session()().get.side_effect = Exception('Some exception message') response = self.controller(self.request) diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/mixins.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/mixins.py index ea9b5b36..f6f60159 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/mixins.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/mixins.py @@ -145,7 +145,7 @@ def test_get_resource_has_permission(self): mock_session = self.farc.get_sessionmaker()() mock_requests_app_user = self.farc.get_app_user_model().get_app_user_from_request() mock_requests_app_user.can_view.return_value = True - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() ret = self.farc.get_resource(request=mock_request, resource_id=mock_resource_id) @@ -161,7 +161,7 @@ def test_get_resource_has_permission_with_session(self): mock_session = mock.MagicMock() mock_requests_app_user = self.farc.get_app_user_model().get_app_user_from_request() mock_requests_app_user.can_view.return_value = True - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() ret = self.farc.get_resource(request=mock_request, resource_id=mock_resource_id, session=mock_session) @@ -192,7 +192,7 @@ def test_get_resource_open_portal(self): mock_request = self.request_factory.get('/foo/bar/') mock_resource_id = self.resource_id mock_session = mock.MagicMock() - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() ret = self.farc.get_resource(request=mock_request, resource_id=mock_resource_id, session=mock_session) @@ -207,7 +207,7 @@ def test_get_resource_db_exception(self): mock_request = self.request_factory.get('/foo/bar/') mock_resource_id = self.resource_id mock_session = self.farc.get_sessionmaker()() - mock_session.query.side_effect = NoResultFound + mock_session.get.side_effect = NoResultFound self.assertRaises(NoResultFound, self.farc.get_resource, request=mock_request, resource_id=mock_resource_id) @@ -241,7 +241,7 @@ def test_get_resource_has_permission(self): mock_session = self.fmrv.get_sessionmaker()() mock_requests_app_user = self.fmrv.get_app_user_model().get_app_user_from_request() mock_requests_app_user.can_view.return_value = True - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() ret = self.fmrv.get_resource(request=mock_request, resource_id=mock_resource_id) @@ -257,7 +257,7 @@ def test_get_resource_has_permission_with_session(self): mock_session = mock.MagicMock() mock_requests_app_user = self.fmrv.get_app_user_model().get_app_user_from_request() mock_requests_app_user.can_view.return_value = True - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() ret = self.fmrv.get_resource(request=mock_request, resource_id=mock_resource_id, session=mock_session) @@ -288,7 +288,7 @@ def test_get_resource_open_portal(self): mock_request = self.request_factory.get('/foo/bar/') mock_resource_id = self.resource_id mock_session = mock.MagicMock() - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() ret = self.fmrv.get_resource(request=mock_request, resource_id=mock_resource_id, session=mock_session) @@ -303,7 +303,7 @@ def test_get_resource_db_exception(self): mock_request = self.request_factory.get('/foo/bar/') mock_resource_id = self.resource_id mock_session = self.fmrv.get_sessionmaker()() - mock_session.query.side_effect = NoResultFound + mock_session.get.side_effect = NoResultFound self.assertRaises(NoResultFound, self.fmrv.get_resource, request=mock_request, resource_id=mock_resource_id) diff --git a/tethysext/atcore/tests/integrated_tests/controllers/app_users/modify_user.py b/tethysext/atcore/tests/integrated_tests/controllers/app_users/modify_user.py index b7fb2742..861c9f7e 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/app_users/modify_user.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/app_users/modify_user.py @@ -78,6 +78,7 @@ def test_post(self, mock_handle_modify_user): # test the results mock_handle_modify_user.assert_called_with(mock_request) + @mock.patch('tethysext.atcore.controllers.app_users.modify_user.select') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.reverse') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.redirect') @mock.patch.object(AppUsersViewMixin, 'get_permissions_manager') @@ -88,7 +89,7 @@ def test_post(self, mock_handle_modify_user): @mock.patch('tethys_apps.utilities.get_active_app') def test__handle_modify_user_requests_post(self, _, mock_get_app_user_model, mock_get_organization_model, mock_get_sessionmaker, mock_get_active_app, - mock_get_permissions_manager, __, mock_reverse): + mock_get_permissions_manager, __, mock_reverse, mock_select): mock_dict = {'modify-user-submit': 'modify-user-submit', 'first-name': 'Foo', 'last-name': 'Bar', 'user-account-status': 'on', 'email': 'user@aquaveo.com', 'password': 'abc123', 'password-confirm': 'abc123', 'assign-role': ['APP_ADMIN', 'DEVELOPER'], @@ -184,6 +185,7 @@ def test__handle_modify_user_requests_post_create_new_client(self, _, mock_get_a self.assertEqual('NameSpace:app_users_manage_users', mock_reverse.call_args_list[0][0][0]) + @mock.patch('tethysext.atcore.controllers.app_users.modify_user.select') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.messages') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.reverse') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.redirect') @@ -194,7 +196,7 @@ def test__handle_modify_user_requests_post_create_new_client(self, _, mock_get_a @mock.patch('tethys_apps.utilities.get_active_app') def test__handle_modify_user_requests_user_not_found_exception(self, _, mock_get_app_user_model, __, mock_get_sessionmaker, mock_get_active_app, ___, - mock_reverse, mock_messages): + mock_reverse, mock_messages, mock_select): mock_dict = {'modify-user-submit': 'modify-user-submit', 'username': 'user1', 'first-name': 'Foo', 'last-name': 'Bar', 'user-account-status': 'on', 'email': 'user@aquaveo.com', 'password': 'abc123', @@ -222,7 +224,7 @@ def test__handle_modify_user_requests_user_not_found_exception(self, _, mock_get mock_get_active_app().url_namespace = 'NameSpace' mock_edit_session = mock_get_sessionmaker()() - mock_edit_session.query().filter().one.side_effect = NoResultFound + mock_edit_session.execute().scalar_one.side_effect = NoResultFound # call method modify_user = ModifyUser() @@ -368,6 +370,7 @@ def test__handle_modify_user_requests_get(self, _, mock_get_app_user_model, mock self.assertFalse(mock_render.call_args_list[0][0][2]['is_me']) self.assertListEqual(['APP_ADMIN', 'DEVELOPER'], mock_render.call_args_list[0][0][2]['no_organization_roles']) + @mock.patch('tethysext.atcore.controllers.app_users.modify_user.select') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.render') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.get_active_app') @mock.patch.object(AppUsersViewMixin, 'get_sessionmaker') @@ -378,7 +381,7 @@ def test__handle_modify_user_requests_post_create_new_client_is_me(self, _, mock __, mock_get_sessionmaker, mock_get_active_app, - mock_render): + mock_render, mock_select): mock_dict = {'modify-user-submit': 'modify-user-submit', 'username': 'user1 sam', 'first-name': 'Foo', 'last-name': 'Bar', @@ -601,6 +604,7 @@ def test__handle_modify_user_requests_post_duplicate_username(self, _, mock_get_ self.assertEqual('Must assign user to at least one organization', mock_render.call_args_list[0][0][2]['organization_select']['error']) + @mock.patch('tethysext.atcore.controllers.app_users.modify_user.select') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.render') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.get_active_app') @mock.patch.object(AppUsersViewMixin, 'get_sessionmaker') @@ -609,7 +613,7 @@ def test__handle_modify_user_requests_post_duplicate_username(self, _, mock_get_ @mock.patch('tethys_apps.utilities.get_active_app') def test__handle_modify_user_requests_validate_edit_confirm_password(self, _, mock_get_app_user_model, __, mock_get_sessionmaker, - mock_get_active_app, mock_render): + mock_get_active_app, mock_render, mock_select): mock_dict = {'modify-user-submit': 'modify-user-submit', 'username': 'user1 sam', 'first-name': 'Foo', 'last-name': 'Bar', 'user-account-status': 'on', 'email': 'user@aquaveo.com', 'password': 'abc123', @@ -672,6 +676,7 @@ def test__handle_modify_user_requests_validate_edit_confirm_password(self, _, mo self.assertEqual('You cannot remove yourself from all organization. You must belong to at least one.', mock_render.call_args_list[0][0][2]['organization_select']['error']) + @mock.patch('tethysext.atcore.controllers.app_users.modify_user.select') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.render') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.get_active_app') @mock.patch.object(AppUsersViewMixin, 'get_sessionmaker') @@ -680,7 +685,8 @@ def test__handle_modify_user_requests_validate_edit_confirm_password(self, _, mo @mock.patch('tethys_apps.utilities.get_active_app') def test__handle_modify_user_requests_validate_edit__password_confirm_password(self, _, mock_get_app_user_model, __, mock_get_sessionmaker, - mock_get_active_app, mock_render): + mock_get_active_app, mock_render, + mock_select): mock_dict = {'modify-user-submit': 'modify-user-submit', 'username': 'user1 sam', 'first-name': 'Foo', 'last-name': 'Bar', 'user-account-status': 'on', 'email': 'user@aquaveo.com', 'password': 'abc123', @@ -743,6 +749,8 @@ def test__handle_modify_user_requests_validate_edit__password_confirm_password(s self.assertEqual('You cannot remove yourself from all organization. You must belong to at least one.', mock_render.call_args_list[0][0][2]['organization_select']['error']) + @mock.patch('tethysext.atcore.services.app_users.decorators.select') + @mock.patch('tethysext.atcore.controllers.app_users.modify_user.select') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.render') @mock.patch('tethysext.atcore.controllers.app_users.modify_user.get_active_app') @mock.patch.object(AppUsersViewMixin, 'get_sessionmaker') @@ -750,7 +758,8 @@ def test__handle_modify_user_requests_validate_edit__password_confirm_password(s @mock.patch.object(AppUsersViewMixin, 'get_app_user_model') @mock.patch('tethys_apps.utilities.get_active_app') def test__handle_modify_normal_user_change_role(self, _, mock_get_app_user_model, __, mock_get_sessionmaker, - mock_get_active_app, mock_render): + mock_get_active_app, mock_render, mock_select, + mock_decorator_select): mock_dict = {'modify-user-submit': 'modify-user-submit', 'first-name': 'Foo', 'last-name': 'Bar', 'username': self.app_user.username, @@ -771,7 +780,7 @@ def test__handle_modify_normal_user_change_role(self, _, mock_get_app_user_model mock_target_user.username = self.app_user.username - session.query().filter().one.return_value = mock_target_user + session.execute().scalar_one.return_value = mock_target_user mock_target_user.get_organizations.return_value = [self.organization] diff --git a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/base.py b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/base.py index 10fd8c16..893304f2 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/base.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/base.py @@ -8,6 +8,7 @@ """ import datetime as dt from unittest import mock +from sqlalchemy import select from tethys_sdk.base import TethysController from tethysext.atcore.services.app_users.roles import Roles from tethysext.atcore.models.app_users.organization import Organization @@ -97,7 +98,7 @@ def test_get_app(self): def test_get_app_user_model(self): app_user_resource_workflow = ResourceWorkflowView() Res = app_user_resource_workflow.get_app_user_model() - a = self.session.query(Res).all() + a = self.session.execute(select(Res)).scalars().all() self.assertEqual('user1', a[0].username) self.assertEqual(Roles.ORG_USER, a[0].role) @@ -109,7 +110,7 @@ def test_get_organization_model(self): def test_get_resource_model(self): app_user_resource_workflow = ResourceWorkflowView() Res = app_user_resource_workflow.get_resource_model() - a = self.session.query(Res).all() + a = self.session.execute(select(Res)).scalars().all() self.assertEqual('eggs', a[0].name) self.assertEqual('for eating', a[0].description) self.assertEqual(Res, type(self.resource)) @@ -201,7 +202,7 @@ def test_get_resource(self, _, mock_app_user, mock_session): resource_out = mock.MagicMock() - session.query().filter().one.return_value = resource_out + session.get.return_value = resource_out # call the method ret = app_user_resource_controller.get_resource(mock_request, self.resource_id) diff --git a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_view_mixins_tests.py b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_view_mixins_tests.py index 1b47f867..893375c8 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_view_mixins_tests.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_view_mixins_tests.py @@ -55,7 +55,7 @@ def test_get_workflow_with_session(self, mock_get_rw_model): ret = self.instance.get_workflow(request=mock.MagicMock(), workflow_id='valid workflow id', session=mock_session) - self.assertEqual(mock_session.query().filter().one(), ret) + self.assertEqual(mock_session.execute().scalar_one(), ret) mock_session.close.assert_not_called() @mock.patch('tethysext.atcore.controllers.resource_workflows.mixins.WorkflowViewMixin.get_resource_workflow_model') @@ -68,7 +68,7 @@ def test_get_workflow_no_session(self, mock_get_rw_model): self.instance.get_sessionmaker.assert_called() self.instance.get_sessionmaker().assert_called() - self.assertEqual(mock_session.query().filter().one(), ret) + self.assertEqual(mock_session.execute().scalar_one(), ret) mock_session.close.assert_called() @mock.patch('tethysext.atcore.controllers.resource_workflows.mixins.WorkflowViewMixin.get_resource_workflow_step_model') # noqa: E501 @@ -78,7 +78,7 @@ def test_get_step_with_session(self, mock_get_rw_step_model): ret = self.instance.get_step(request=mock.MagicMock(), step_id='valid step id', session=mock_session) - self.assertEqual(mock_session.query().filter().one(), ret) + self.assertEqual(mock_session.execute().scalar_one(), ret) mock_session.close.assert_not_called() @mock.patch('tethysext.atcore.controllers.resource_workflows.mixins.WorkflowViewMixin.get_resource_workflow_step_model') # noqa: E501 @@ -91,5 +91,5 @@ def test_get_step_no_session(self, mock_get_rw_step_model): self.instance.get_sessionmaker.assert_called() self.instance.get_sessionmaker().assert_called() - self.assertEqual(mock_session.query().filter().one(), ret) + self.assertEqual(mock_session.execute().scalar_one(), ret) mock_session.close.assert_called() diff --git a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/set_status_wv_tests.py b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/set_status_wv_tests.py index bd951ab7..82106949 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/set_status_wv_tests.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/set_status_wv_tests.py @@ -1,5 +1,6 @@ from unittest import mock +from sqlalchemy import select from django.http import HttpRequest from tethysext.atcore.tests.factories.django_user import UserFactory from django.test import RequestFactory @@ -247,7 +248,7 @@ def test_process_step_data_ssrws_no_options(self): self.assertEqual(self.mock_psd(), ret) - step = self.session.query(SetStatusRWS).filter(SetStatusRWS.name == 'ssrws_no_options').one() + step = self.session.execute(select(SetStatusRWS).where(SetStatusRWS.name == 'ssrws_no_options')).scalar_one() self.assertEqual(SetStatusRWS.STATUS_COMPLETE, step.get_status()) self.assertEqual(comment, step.get_parameter('comments')) diff --git a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/xms_tool_wv_tests.py b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/xms_tool_wv_tests.py index 59c76131..c8eef190 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/xms_tool_wv_tests.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/resource_workflows/workflow_views/xms_tool_wv_tests.py @@ -1,6 +1,7 @@ from unittest import mock import param +from sqlalchemy import select from django.http import HttpRequest from tethysext.atcore.controllers.resource_workflows.workflow_view import ResourceWorkflowView @@ -188,7 +189,7 @@ def test_process_step_data_basic(self, mock_gen_form): ) self.assertEqual(self.mock_psd(), ret) - step = self.session.query(XMSToolRWS).filter(XMSToolRWS.name == 'xrws').one() + step = self.session.execute(select(XMSToolRWS).where(XMSToolRWS.name == 'xrws')).scalar_one() self.assertEqual({'value': {'integer_val': '1', 'float_val': '2.0', 'string_val': '3'}}, step.get_parameter('form-values')) diff --git a/tethysext/atcore/tests/integrated_tests/controllers/resources/tabs/workflows_tab_tests.py b/tethysext/atcore/tests/integrated_tests/controllers/resources/tabs/workflows_tab_tests.py index 666fb209..fdcf9e92 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/resources/tabs/workflows_tab_tests.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/resources/tabs/workflows_tab_tests.py @@ -8,6 +8,7 @@ """ from unittest import mock +from sqlalchemy import select from django.test import RequestFactory from django.http import JsonResponse from tethys_apps.models import TethysApp @@ -533,7 +534,7 @@ def test_post_ideal(self): self.mock_messages.success.assert_called_with(request, 'Successfully created new Basic Workflow: New Workflow') self.assertEqual(self.mock_redirect(), ret) - self.session.query(ResourceWorkflow).filter(ResourceWorkflow.name == 'New Workflow').one() + self.session.execute(select(ResourceWorkflow).where(ResourceWorkflow.name == 'New Workflow')).scalar_one() def test_post_new_workflow_not_in_params(self): """Test new workflow form submissions with missing new-workflow parameter.""" @@ -619,7 +620,7 @@ def test_delete_ideal(self): self.mock_log.info.assert_called_with(f'Deleted Workflow: {self.workflow}') self.assertIsInstance(ret, JsonResponse) self.assertEqual(b'{"success": true}', ret.content) - self.assertIsNone(self.session.query(ResourceWorkflow).get(workflow_id)) + self.assertIsNone(self.session.get(ResourceWorkflow, workflow_id)) def test_delete_exception(self): """Test delete workflows requests with exception occurring.""" diff --git a/tethysext/atcore/tests/integrated_tests/controllers/rest/spatial_reference.py b/tethysext/atcore/tests/integrated_tests/controllers/rest/spatial_reference.py index 95d512b3..82fabc1b 100644 --- a/tethysext/atcore/tests/integrated_tests/controllers/rest/spatial_reference.py +++ b/tethysext/atcore/tests/integrated_tests/controllers/rest/spatial_reference.py @@ -7,7 +7,7 @@ ******************************************************************************** """ import json -from sqlalchemy import create_engine +from sqlalchemy import create_engine, text from django.test import RequestFactory from tethys_sdk.testing import TethysTestCase from tethysext.atcore.tests.factories.django_user import UserFactory @@ -20,7 +20,8 @@ def setUpModule(): # Connect to the database and create the schema within a transaction engine = create_engine(TEST_DB_URL) - engine.execute('CREATE EXTENSION IF NOT EXISTS postgis;') + with engine.begin() as connection: + connection.execute(text('CREATE EXTENSION IF NOT EXISTS postgis;')) def tearDownModule(): diff --git a/tethysext/atcore/tests/integrated_tests/mixins/file_collection_mixin_tests.py b/tethysext/atcore/tests/integrated_tests/mixins/file_collection_mixin_tests.py index e6fa6624..37659e91 100644 --- a/tethysext/atcore/tests/integrated_tests/mixins/file_collection_mixin_tests.py +++ b/tethysext/atcore/tests/integrated_tests/mixins/file_collection_mixin_tests.py @@ -3,6 +3,7 @@ import shutil import uuid +from sqlalchemy import select, func from sqlalchemy.orm import relationship, backref from tethysext.atcore.mixins.file_collection_mixin import FileCollectionMixin @@ -115,8 +116,8 @@ def test_new(self): file_database_client=database_client, files=files ) - collection_count = self.session.query(FileCollection).count() - resource_count = self.session.query(TestResourceWithFiles).count() + collection_count = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count, 1) self.assertEqual(resource_count, 1) self.assertTrue(len(resource.file_collections) == 1) @@ -137,8 +138,8 @@ def test_new_multiple_files(self): os.path.join(self.root_dir, 'files', 'file2.txt'), ] resource = TestResourceWithFiles.new(database_client, files) - collection_count = self.session.query(FileCollection).count() - resource_count = self.session.query(TestResourceWithFiles).count() + collection_count = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count, 1) self.assertEqual(resource_count, 1) self.assertTrue(len(resource.file_collections) == 1) @@ -156,8 +157,8 @@ def test_new_multiple_collections(self): os.path.join(self.root_dir, 'files', 'file2.txt'), ] resource = TestResourceWithFiles.new(database_client, files, separate_collections=True) - collection_count = self.session.query(FileCollection).count() - resource_count = self.session.query(TestResourceWithFiles).count() + collection_count = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count, 2) self.assertEqual(resource_count, 1) self.assertTrue(len(resource.file_collections) == 2) @@ -169,8 +170,8 @@ def test_new_directory(self): os.path.join(self.root_dir, 'files', 'dir1'), ] resource = TestResourceWithFiles.new(database_client, files) - collection_count = self.session.query(FileCollection).count() - resource_count = self.session.query(TestResourceWithFiles).count() + collection_count = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count, 1) self.assertEqual(resource_count, 1) self.assertTrue(len(resource.file_collections) == 1) @@ -236,13 +237,13 @@ def test_duplicate(self): os.path.join(self.root_dir, 'files', 'file2.txt'), ] resource = TestResourceWithFiles.new(database_client, files) - collection_count_before = self.session.query(FileCollection).count() - resource_count_before = self.session.query(TestResourceWithFiles).count() + collection_count_before = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count_before = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count_before, 1) self.assertEqual(resource_count_before, 1) _ = resource.duplicate(database_client) - collection_count_after = self.session.query(FileCollection).count() - resource_count_after = self.session.query(TestResourceWithFiles).count() + collection_count_after = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count_after = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count_after, 2) self.assertEqual(resource_count_after, 2) @@ -254,13 +255,13 @@ def test_duplicate_multiple_collections(self): os.path.join(self.root_dir, 'files', 'file2.txt'), ] resource = TestResourceWithFiles.new(database_client, files, separate_collections=True) - collection_count_before = self.session.query(FileCollection).count() - resource_count_before = self.session.query(TestResourceWithFiles).count() + collection_count_before = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count_before = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count_before, 2) self.assertEqual(resource_count_before, 1) _ = resource.duplicate(database_client) - collection_count_after = self.session.query(FileCollection).count() - resource_count_after = self.session.query(TestResourceWithFiles).count() + collection_count_after = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count_after = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count_after, 4) self.assertEqual(resource_count_after, 2) @@ -272,12 +273,12 @@ def test_delete_collections(self): os.path.join(self.root_dir, 'files', 'file2.txt'), ] resource = TestResourceWithFiles.new(database_client, files) - collection_count_before = self.session.query(FileCollection).count() - resource_count_before = self.session.query(TestResourceWithFiles).count() + collection_count_before = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count_before = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count_before, 1) self.assertEqual(resource_count_before, 1) resource.delete_collections(self.root_dir) - collection_count_after = self.session.query(FileCollection).count() + collection_count_after = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() self.assertEqual(collection_count_after, 0) def test_delete_collections_multiple_collections(self): @@ -288,10 +289,10 @@ def test_delete_collections_multiple_collections(self): os.path.join(self.root_dir, 'files', 'file2.txt'), ] resource = TestResourceWithFiles.new(database_client, files, separate_collections=True) - collection_count_before = self.session.query(FileCollection).count() - resource_count_before = self.session.query(TestResourceWithFiles).count() + collection_count_before = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() + resource_count_before = self.session.execute(select(func.count()).select_from(TestResourceWithFiles)).scalar() self.assertEqual(collection_count_before, 2) self.assertEqual(resource_count_before, 1) resource.delete_collections(self.root_dir) - collection_count_after = self.session.query(FileCollection).count() + collection_count_after = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() self.assertEqual(collection_count_after, 0) diff --git a/tethysext/atcore/tests/integrated_tests/models/app_users/app_user_tests.py b/tethysext/atcore/tests/integrated_tests/models/app_users/app_user_tests.py index 9ab4bcd7..329773fa 100644 --- a/tethysext/atcore/tests/integrated_tests/models/app_users/app_user_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/app_users/app_user_tests.py @@ -1,5 +1,6 @@ import uuid from django.contrib.auth.models import User +from sqlalchemy import func, select from unittest.mock import patch, MagicMock from tethys_sdk.base import TethysController from tethysext.atcore.models.app_users import AppUser, Organization, Resource @@ -168,9 +169,9 @@ def setUp(self): self.session.add(self.rsrc3) self.session.commit() - self.staff_user = self.session.query(AppUser). \ - filter(AppUser.username == AppUser.STAFF_USERNAME). \ - one() + self.staff_user = self.session.execute( + select(AppUser).where(AppUser.username == AppUser.STAFF_USERNAME) + ).scalar_one() self.staff_user_request = MockDjangoRequest( user_username="im_staff", @@ -197,7 +198,7 @@ def setUp(self): ) def test_create_user(self): - user = self.session.query(AppUser).get(self.user_id) + user = self.session.get(AppUser, self.user_id) self.assertEqual(user.username, self.username) self.assertEqual(user.role, self.role) self.assertEqual(user.is_active, self.is_active) @@ -760,56 +761,56 @@ def test_delete_existing_settings(self): settings = self._init_settings_same_keys() settings_to_delete = [settings[0], settings[2]] self.user.delete_existing_settings(self.session, settings_to_delete) - count = self.session.query(UserSetting).count() + count = self.session.execute(select(func.count()).select_from(UserSetting)).scalar() self.assertEqual(2, count) def test_update_setting(self): self._init_settings_same_keys_same_values() self.user.update_setting(self.session, 'one', '2') - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(1, len(settings)) def test_update_setting_non_existing(self): self.user.update_setting(self.session, 'one', '2') - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(1, len(settings)) def test_update_setting_resource(self): self._init_settings_same_keys_same_values() self.user.update_setting(self.session, 'one', '2', resource=self.rsrc1) - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(1, len(settings)) def test_update_setting_secondary_id(self): self._init_settings_same_keys_same_values() self.user.update_setting(self.session, 'one', '2', secondary_id='another-id') - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(1, len(settings)) def test_update_setting_page(self): self._init_settings_same_keys_same_values() self.user.update_setting(self.session, 'one', '2', page='a_page') - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(1, len(settings)) def test_update_setting_no_commit(self): self._init_settings_same_keys_same_values() self.user.update_setting(self.session, 'one', '2', commit=False) self.session.rollback() - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(0, len(settings)) def test_update_setting_multiple_times(self): self.user.update_setting(self.session, 'one', '1') - settings = self.session.query(UserSetting).filter(UserSetting.value == '1').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '1')).scalars().all() self.assertEqual(1, len(settings)) self.user.update_setting(self.session, 'one', '2') - settings = self.session.query(UserSetting).filter(UserSetting.value == '2').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '2')).scalars().all() self.assertEqual(1, len(settings)) self.user.update_setting(self.session, 'one', '3') - settings = self.session.query(UserSetting).filter(UserSetting.value == '3').all() + settings = self.session.execute(select(UserSetting).where(UserSetting.value == '3')).scalars().all() self.assertEqual(1, len(settings)) - all_one_settings = self.session.query(UserSetting).filter(UserSetting.key == 'one').all() + all_one_settings = self.session.execute(select(UserSetting).where(UserSetting.key == 'one')).scalars().all() self.assertEqual(1, len(all_one_settings)) @patch('tethys_sdk.permissions.has_permission', side_effect=mock_has_permission_false) diff --git a/tethysext/atcore/tests/integrated_tests/models/app_users/organization_tests.py b/tethysext/atcore/tests/integrated_tests/models/app_users/organization_tests.py index 9eae9f26..39c110b3 100644 --- a/tethysext/atcore/tests/integrated_tests/models/app_users/organization_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/app_users/organization_tests.py @@ -1,4 +1,5 @@ import uuid +from sqlalchemy import select from unittest.mock import patch from tethysext.atcore.models.app_users import Organization, AppUser, Resource from tethysext.atcore.tests.mock.permissions import mock_has_permission_false @@ -226,13 +227,19 @@ def test_must_have_consultant(self): def test_receive_before_delete(self): self.session.delete(self.organization) self.session.commit() - staff_user = self.session.query(AppUser).filter(AppUser.id == self.staff_user_id).one_or_none() + staff_user = self.session.execute( + select(AppUser).where(AppUser.id == self.staff_user_id) + ).scalar_one_or_none() self.assertIsNotNone(staff_user) - normal_user = self.session.query(AppUser).filter(AppUser.id == self.normal_user_id).one_or_none() + normal_user = self.session.execute( + select(AppUser).where(AppUser.id == self.normal_user_id) + ).scalar_one_or_none() self.assertIsNone(normal_user) - resource = self.session.query(Resource).filter(Resource.id == self.resource_id).one_or_none() + resource = self.session.execute( + select(Resource).where(Resource.id == self.resource_id) + ).scalar_one_or_none() self.assertIsNone(resource) def test_is_member_true(self): diff --git a/tethysext/atcore/tests/integrated_tests/models/app_users/resource_tests.py b/tethysext/atcore/tests/integrated_tests/models/app_users/resource_tests.py index 9eed927c..e84227f1 100644 --- a/tethysext/atcore/tests/integrated_tests/models/app_users/resource_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/app_users/resource_tests.py @@ -1,5 +1,6 @@ import datetime import uuid +from sqlalchemy import func, select from tethysext.atcore.models.app_users import Resource from tethysext.atcore.tests.utilities.sqlalchemy_helpers import SqlAlchemyTestCase from tethysext.atcore.tests.utilities.sqlalchemy_helpers import setup_module_for_sqlalchemy_tests, \ @@ -33,8 +34,8 @@ def test_create_resource(self): self.session.add(resource) self.session.commit() - all_resources_count = self.session.query(Resource).count() - all_resources = self.session.query(Resource).all() + all_resources_count = self.session.execute(select(func.count()).select_from(Resource)).scalar() + all_resources = self.session.execute(select(Resource)).scalars().all() self.assertEqual(all_resources_count, 1) for resource in all_resources: diff --git a/tethysext/atcore/tests/integrated_tests/models/app_users/resource_workflow_step_tests.py b/tethysext/atcore/tests/integrated_tests/models/app_users/resource_workflow_step_tests.py index aab3fd6f..828e495e 100644 --- a/tethysext/atcore/tests/integrated_tests/models/app_users/resource_workflow_step_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/app_users/resource_workflow_step_tests.py @@ -237,7 +237,7 @@ def test_reset(self, mock_init_parameters): self.step.reset() self.session.commit() - q_instance = self.session.query(ResourceWorkflowStep).get(id) + q_instance = self.session.get(ResourceWorkflowStep, id) self.assertFalse(q_instance.dirty) self.assertEqual(ResourceWorkflowStep.STATUS_PENDING, diff --git a/tethysext/atcore/tests/integrated_tests/models/app_users/spatial_resource_tests.py b/tethysext/atcore/tests/integrated_tests/models/app_users/spatial_resource_tests.py index eb1eca67..968abc62 100644 --- a/tethysext/atcore/tests/integrated_tests/models/app_users/spatial_resource_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/app_users/spatial_resource_tests.py @@ -2,7 +2,7 @@ import json import uuid -from sqlalchemy import func +from sqlalchemy import func, select from tethysext.atcore.exceptions import InvalidSpatialResourceExtentTypeError from tethysext.atcore.models.app_users import SpatialResource @@ -51,12 +51,12 @@ def setUp(self): '-109.528369903564 40.4484166930046,-109.528756141663 40.4629153531036,-109.557466506958 ' \ '40.4626541436629))' - qry = self.session.query(func.ST_GeomFromEWKT(self.extent_wkt).label('geom')) + qry = self.session.execute(select(func.ST_GeomFromEWKT(self.extent_wkt).label('geom'))) self.expected_geometry = qry.first().geom def compare_geometries(self, geom_a, geom_b): - text_a = self.session.query(func.ST_AsText(geom_a).label('text')) - text_b = self.session.query(func.ST_AsText(geom_b).label('text')) + text_a = self.session.execute(select(func.ST_AsText(geom_a).label('text'))) + text_b = self.session.execute(select(func.ST_AsText(geom_b).label('text'))) self.assertEqual(text_a.first().text, text_b.first().text) def create_resource(self): @@ -86,8 +86,8 @@ def test_create_resource(self): self.session.add(resource) self.session.commit() - all_resources_count = self.session.query(SpatialResource).count() - all_resources = self.session.query(SpatialResource).all() + all_resources_count = self.session.execute(select(func.count()).select_from(SpatialResource)).scalar() + all_resources = self.session.execute(select(SpatialResource)).scalars().all() self.assertEqual(all_resources_count, 1) for resource in all_resources: @@ -175,12 +175,12 @@ def test_update_extent_srid(self): resource = self.create_resource_in_session() resource.extent = self.expected_geometry - wkt_extent_query_before = self.session.query(func.ST_AsEWKT(resource.extent)).first() + wkt_extent_query_before = self.session.execute(select(func.ST_AsEWKT(resource.extent))).first() ret_before = wkt_extent_query_before[0] self.assertNotIn('SRID=3857', ret_before) resource.update_extent_srid(3857) - wkt_extent_query_after = self.session.query(func.ST_AsEWKT(resource.extent)).first() + wkt_extent_query_after = self.session.execute(select(func.ST_AsEWKT(resource.extent))).first() ret_after = wkt_extent_query_after[0] self.assertIn('SRID=3857', ret_after) diff --git a/tethysext/atcore/tests/integrated_tests/models/files_database/file_collection_tests.py b/tethysext/atcore/tests/integrated_tests/models/files_database/file_collection_tests.py index c65cb983..0cfd5b32 100644 --- a/tethysext/atcore/tests/integrated_tests/models/files_database/file_collection_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/files_database/file_collection_tests.py @@ -31,6 +31,6 @@ def test_database_round_trip(self): meta={"TestKey": "TestValue"}) self.session.add(new_instance) self.session.commit() - instance_from_db = self.session.query(FileCollection).get(new_instance.id) + instance_from_db = self.session.get(FileCollection, new_instance.id) self.assertEqual(new_instance.file_database_id, instance_from_db.file_database_id) self.assertEqual(new_instance.meta, instance_from_db.meta) diff --git a/tethysext/atcore/tests/integrated_tests/models/files_database/file_database_tests.py b/tethysext/atcore/tests/integrated_tests/models/files_database/file_database_tests.py index ea27e282..28ffa269 100644 --- a/tethysext/atcore/tests/integrated_tests/models/files_database/file_database_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/files_database/file_database_tests.py @@ -19,5 +19,5 @@ def test_database_round_trip(self): new_instance = FileDatabase(meta={"TestKey": "TestValue"}) self.session.add(new_instance) self.session.commit() - instance_from_db = self.session.query(FileDatabase).get(new_instance.id) + instance_from_db = self.session.get(FileDatabase, new_instance.id) self.assertDictEqual(new_instance.meta, instance_from_db.meta) diff --git a/tethysext/atcore/tests/integrated_tests/models/initializer.py b/tethysext/atcore/tests/integrated_tests/models/initializer.py index 7fef1deb..c6652a6d 100644 --- a/tethysext/atcore/tests/integrated_tests/models/initializer.py +++ b/tethysext/atcore/tests/integrated_tests/models/initializer.py @@ -1,4 +1,5 @@ from tethys_sdk.testing import TethysTestCase +from sqlalchemy import select from sqlalchemy.engine import create_engine from sqlalchemy.orm.session import Session @@ -25,9 +26,9 @@ def test_initialize_app_users_db_vanilla(self): initialize_app_users_db(self.connection) session = Session(self.connection) - staff_user = session.query(AppUser). \ - filter(AppUser.username == AppUser.STAFF_USERNAME). \ - one_or_none() + staff_user = session.execute( + select(AppUser).where(AppUser.username == AppUser.STAFF_USERNAME) + ).scalar_one_or_none() self.assertIsNotNone(staff_user) self.assertEqual(AppUser.ROLES.DEVELOPER, staff_user.role) session.close() diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/results_rws_tests.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/results_rws_tests.py index cc8a980d..c14b7b01 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/results_rws_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/results_rws_tests.py @@ -23,7 +23,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(ResultsResourceWorkflowStep).get(self.instance.id) + ret = self.session.get(ResultsResourceWorkflowStep, self.instance.id) self.assertEqual(self.instance, ret) def test_default_options(self): diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_attributes_rws_tests.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_attributes_rws_tests.py index b9e0192e..af81d329 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_attributes_rws_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_attributes_rws_tests.py @@ -24,7 +24,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(SpatialAttributesRWS).get(self.instance.id) + ret = self.session.get(SpatialAttributesRWS, self.instance.id) self.assertEqual(self.instance, ret) def test_default_options(self): diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_condor_job_rws_test.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_condor_job_rws_test.py index c3ea544f..eb83bdad 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_condor_job_rws_test.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_condor_job_rws_test.py @@ -23,7 +23,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(SpatialCondorJobRWS).get(self.instance.id) + ret = self.session.get(SpatialCondorJobRWS, self.instance.id) self.assertEqual(self.instance, ret) def test_default_options(self): diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_dataset_rws_tests.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_dataset_rws_tests.py index 69b7311a..b84b0bc5 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_dataset_rws_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_dataset_rws_tests.py @@ -39,7 +39,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(SpatialDatasetRWS).get(self.instance.id) + ret = self.session.get(SpatialDatasetRWS, self.instance.id) self.assertEqual(self.instance, ret) def test_default_options(self): diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_input_rws_tests.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_input_rws_tests.py index 34f5b52a..2ea89d07 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_input_rws_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_input_rws_tests.py @@ -35,7 +35,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(SpatialInputRWS).get(self.instance.id) + ret = self.session.get(SpatialInputRWS, self.instance.id) self.assertEqual(self.instance, ret) def test_default_options(self): diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_rws_tests.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_rws_tests.py index 3c841671..cfa56249 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_rws_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/spatial_rws_tests.py @@ -24,7 +24,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(SpatialResourceWorkflowStep).get(self.instance.id) + ret = self.session.get(SpatialResourceWorkflowStep, self.instance.id) self.assertEqual(self.instance, ret) @mock.patch('tethysext.atcore.models.resource_workflow_steps.spatial_rws.SpatialResourceWorkflowStep.get_parameter') diff --git a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/table_input_rws_tests.py b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/table_input_rws_tests.py index da3fd415..cebdd13f 100644 --- a/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/table_input_rws_tests.py +++ b/tethysext/atcore/tests/integrated_tests/models/resource_workflow_steps/table_input_rws_tests.py @@ -38,7 +38,7 @@ def setUp(self): def test_query(self): self.session.add(self.instance) self.session.commit() - ret = self.session.query(TableInputRWS).get(self.instance.id) + ret = self.session.get(TableInputRWS, self.instance.id) self.assertEqual(self.instance, ret) self.session.delete(self.instance) self.session.commit() diff --git a/tethysext/atcore/tests/integrated_tests/services/app_users/condor_workflow_manager_tests.py b/tethysext/atcore/tests/integrated_tests/services/app_users/condor_workflow_manager_tests.py index 32797e07..78cf0f9e 100644 --- a/tethysext/atcore/tests/integrated_tests/services/app_users/condor_workflow_manager_tests.py +++ b/tethysext/atcore/tests/integrated_tests/services/app_users/condor_workflow_manager_tests.py @@ -268,7 +268,7 @@ def test_prepare(self): 'testkey' ] - self._assert_prepared_jobs(manager, 1, expected_job_args) + self._assert_prepared_jobs(manager, expected_job_args) @mock.patch('tethys_compute.models.tethys_job.TethysJob.execute') def test_run_job_prepared(self, _): @@ -279,7 +279,12 @@ def test_run_job_prepared(self, _): id = manager.run_job() - self.assertEqual(str(2), id) + # run_job returns the CondorWorkflow's auto-incrementing id as a string. + # The exact value depends on the test database's sequence state, so just + # assert the id is a non-empty stringified positive integer. + self.assertIsNotNone(id) + self.assertTrue(str(id).isdigit()) + self.assertGreater(int(id), 0) def test_run_prepare_with_callback_function(self): def callback_func(manager): @@ -302,15 +307,20 @@ def callback_func(manager): 'testkey' ] - self._assert_prepared_jobs(manager, 3, expected_job_args) + self._assert_prepared_jobs(manager, expected_job_args) - def _assert_prepared_jobs(self, manager, expected_workflow_id, expected_job_args): + def _assert_prepared_jobs(self, manager, expected_job_args): id = manager.prepare() + # All jobs should reference the same CondorWorkflow; the exact id + # value is whatever PostgreSQL's sequence happens to assign. + workflow_id = manager.jobs[0].workflow.id + self.assertIsNotNone(workflow_id) + # Job 1 (use_atcore_args is not specified, defaults to True) self.assertEqual(self.jobs[0]['name'], manager.jobs[0].name) - self.assertEqual(expected_workflow_id, manager.jobs[0].workflow.id) - self.assertEqual(expected_workflow_id, manager.jobs[0].workflow_id) + self.assertEqual(workflow_id, manager.jobs[0].workflow.id) + self.assertEqual(workflow_id, manager.jobs[0].workflow_id) self.assertEqual(self.jobs[0]['name'], manager.jobs[0]._attributes['job_name']) self.assertEqual('vanilla', manager.jobs[0]._attributes['universe']) self.assertEqual('run_base_scenario.py', manager.jobs[0]._attributes['executable']) @@ -323,8 +333,8 @@ def _assert_prepared_jobs(self, manager, expected_workflow_id, expected_job_args # Job 2 (use_atcore_args is True) self.assertEqual(self.jobs[1]['name'], manager.jobs[1].name) - self.assertEqual(expected_workflow_id, manager.jobs[1].workflow.id) - self.assertEqual(expected_workflow_id, manager.jobs[1].workflow_id) + self.assertEqual(workflow_id, manager.jobs[1].workflow.id) + self.assertEqual(workflow_id, manager.jobs[1].workflow_id) self.assertEqual(self.jobs[1]['name'], manager.jobs[1]._attributes['job_name']) self.assertEqual('vanilla', manager.jobs[1]._attributes['universe']) self.assertEqual('run_detention_basin_scenario.py', manager.jobs[1]._attributes['executable']) @@ -335,8 +345,8 @@ def _assert_prepared_jobs(self, manager, expected_workflow_id, expected_job_args # Job 3 (use_atcore_args is False) self.assertEqual(self.jobs[2]['name'], manager.jobs[2].name) - self.assertEqual(expected_workflow_id, manager.jobs[2].workflow.id) - self.assertEqual(expected_workflow_id, manager.jobs[2].workflow_id) + self.assertEqual(workflow_id, manager.jobs[2].workflow.id) + self.assertEqual(workflow_id, manager.jobs[2].workflow_id) self.assertEqual(self.jobs[2]['name'], manager.jobs[2]._attributes['job_name']) self.assertEqual('vanilla', manager.jobs[2]._attributes['universe']) self.assertEqual('post_process.py', manager.jobs[2]._attributes['executable']) @@ -348,8 +358,8 @@ def _assert_prepared_jobs(self, manager, expected_workflow_id, expected_job_args # Job 4 (finalize job, use_atcore_args is not specified, defaults to True) self.assertEqual('finalize', manager.jobs[3].name) - self.assertEqual(expected_workflow_id, manager.jobs[3].workflow.id) - self.assertEqual(expected_workflow_id, manager.jobs[3].workflow_id) + self.assertEqual(workflow_id, manager.jobs[3].workflow.id) + self.assertEqual(workflow_id, manager.jobs[3].workflow_id) self.assertEqual('finalize', manager.jobs[3]._attributes['job_name']) self.assertEqual('vanilla', manager.jobs[3]._attributes['universe']) self.assertEqual('update_status.py', manager.jobs[3]._attributes['executable']) @@ -357,7 +367,7 @@ def _assert_prepared_jobs(self, manager, expected_workflow_id, expected_job_args self.assertEqual('../workflow_params.json', manager.jobs[3]._attributes['transfer_input_files']) self.assertEqual('', manager.jobs[3]._attributes['transfer_output_files']) - self.assertEqual(expected_workflow_id, id) + self.assertEqual(workflow_id, id) self.assertIsInstance(manager.workflow, CondorWorkflow) self.assertEqual(expected_job_args, manager.job_args) self.assertTrue(manager.prepared) diff --git a/tethysext/atcore/tests/integrated_tests/services/app_users/permissions_manager.py b/tethysext/atcore/tests/integrated_tests/services/app_users/permissions_manager.py index b8db21d4..a88daef3 100644 --- a/tethysext/atcore/tests/integrated_tests/services/app_users/permissions_manager.py +++ b/tethysext/atcore/tests/integrated_tests/services/app_users/permissions_manager.py @@ -7,6 +7,7 @@ ******************************************************************************** """ from django.contrib.auth.models import User, Group +from sqlalchemy import select from tethysext.atcore.models.app_users import AppUser from tethysext.atcore.services.app_users.roles import Roles from tethysext.atcore.services.app_users.licenses import Licenses @@ -52,7 +53,9 @@ def setUp(self): email="teva@aquaveo.com", password="pass" ) - self.staff_app_user = self.session.query(AppUser).filter(AppUser.username == AppUser.STAFF_USERNAME).one() + self.staff_app_user = self.session.execute( + select(AppUser).where(AppUser.username == AppUser.STAFF_USERNAME) + ).scalar_one() # Permissions manager setup self.url_namespace = 'foo' diff --git a/tethysext/atcore/tests/integrated_tests/services/file_database/file_collection_client_tests.py b/tethysext/atcore/tests/integrated_tests/services/file_database/file_collection_client_tests.py index f866ef6e..7d254c02 100644 --- a/tethysext/atcore/tests/integrated_tests/services/file_database/file_collection_client_tests.py +++ b/tethysext/atcore/tests/integrated_tests/services/file_database/file_collection_client_tests.py @@ -3,6 +3,8 @@ import shutil import uuid +from sqlalchemy import func, select + from tethysext.atcore.exceptions import FileCollectionNotFoundError, UnboundFileCollectionError, \ FileCollectionItemNotFoundError, FileCollectionItemAlreadyExistsError from tethysext.atcore.services.file_database import FileDatabaseClient, FileCollectionClient @@ -82,9 +84,9 @@ def test_new_file_collection_client(self): if os.path.exists(root_dir): shutil.rmtree(root_dir) database_client = FileDatabaseClient.new(self.session, root_dir) - self.assertTrue(self.session.query(FileCollection).count() == 0) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileCollection)).scalar() == 0) collection_client = FileCollectionClient.new(self.session, database_client) - self.assertTrue(self.session.query(FileCollection).count() == 1) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileCollection)).scalar() == 1) self.assertTrue(os.path.exists(collection_client.path)) def test_path_property(self): @@ -278,7 +280,7 @@ def test_set_meta(self): collection_client = FileCollectionClient(self.session, database_client, collection_id) collection_client.set_meta('Key3', 'NewValue') - altered_collection = self.session.query(FileCollection).get(collection_id) + altered_collection = self.session.get(FileCollection, collection_id) self.assertEqual(altered_collection.meta.get('Key3', None), 'NewValue') def test_set_meta_new_value(self): @@ -296,7 +298,7 @@ def test_set_meta_new_value(self): collection_client = FileCollectionClient(self.session, database_client, collection_id) collection_client.set_meta('NewKey', 'AddedValue') - altered_collection = self.session.query(FileCollection).get(collection_id) + altered_collection = self.session.get(FileCollection, collection_id) self.assertEqual(altered_collection.meta.get('NewKey', None), 'AddedValue') def test_collection_delete(self): diff --git a/tethysext/atcore/tests/integrated_tests/services/file_database/file_database_client_tests.py b/tethysext/atcore/tests/integrated_tests/services/file_database/file_database_client_tests.py index ef69bac1..4fbc4720 100644 --- a/tethysext/atcore/tests/integrated_tests/services/file_database/file_database_client_tests.py +++ b/tethysext/atcore/tests/integrated_tests/services/file_database/file_database_client_tests.py @@ -3,6 +3,8 @@ from unittest import mock import uuid +from sqlalchemy import func, select + from tethysext.atcore.exceptions import FileDatabaseNotFoundError, FileCollectionNotFoundError, UnboundFileDatabaseError from tethysext.atcore.services.file_database import FileCollectionClient, FileDatabaseClient from tethysext.atcore.models.file_database import FileCollection, FileDatabase @@ -66,10 +68,10 @@ def get_collection_instance(self, collection_id, database_id, collection_meta): def test_new_file_database_client(self): """Test the new function on the file database client.""" - self.assertTrue(self.session.query(FileDatabase).count() == 0) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileDatabase)).scalar() == 0) root_dir = os.path.join(self.test_files_base, 'temp', 'test_new_file_database_client') database_client = FileDatabaseClient.new(self.session, root_dir) - self.assertTrue(self.session.query(FileDatabase).count() == 1) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileDatabase)).scalar() == 1) self.assertTrue(os.path.exists(database_client.path)) def test_existing_file_database_client(self): @@ -228,7 +230,7 @@ def test_set_meta(self): database_client = FileDatabaseClient(self.session, root_dir, database_id) database_client.set_meta('Key3', 'NewValue') - altered_collection = self.session.query(FileDatabase).get(database_id) + altered_collection = self.session.get(FileDatabase, database_id) self.assertEqual(altered_collection.meta.get('Key3', None), 'NewValue') def test_set_meta_new_value(self): @@ -242,7 +244,7 @@ def test_set_meta_new_value(self): database_client = FileDatabaseClient(self.session, root_dir, database_id) database_client.set_meta('NewKey', 'AddedValue') - altered_collection = self.session.query(FileDatabase).get(database_id) + altered_collection = self.session.get(FileDatabase, database_id) self.assertEqual(altered_collection.meta.get('NewKey', None), 'AddedValue') def test_new_collection(self): @@ -259,7 +261,7 @@ def test_new_collection(self): ) database_client = FileDatabaseClient(self.session, root_dir, database_id) collection_client = database_client.new_collection() - new_file_collection = self.session.query(FileCollection).get(collection_client.instance.id) + new_file_collection = self.session.get(FileCollection, collection_client.instance.id) self.assertTrue(new_file_collection is not None) self.assertTrue(os.path.exists(os.path.join(database_client.path, str(collection_client.instance.id)))) @@ -281,7 +283,7 @@ def test_new_collection_with_files(self): ] database_client = FileDatabaseClient(self.session, root_dir, database_id) collection_client = database_client.new_collection(items=collection_files) - new_file_collection = self.session.query(FileCollection).get(collection_client.instance.id) + new_file_collection = self.session.get(FileCollection, collection_client.instance.id) self.assertTrue(new_file_collection is not None) self.assertTrue(os.path.exists(os.path.join(database_client.path, str(collection_client.instance.id)))) self.assertTrue(os.path.exists(os.path.join(collection_client.path, 'file1.txt'))) @@ -309,7 +311,7 @@ def test_new_collection_with_files_not_relative_to(self): ] database_client = FileDatabaseClient(self.session, root_dir, database_id) collection_client = database_client.new_collection(items=collection_files) - new_file_collection = self.session.query(FileCollection).get(collection_client.instance.id) + new_file_collection = self.session.get(FileCollection, collection_client.instance.id) self.assertTrue(new_file_collection is not None) self.assertTrue(os.path.exists(os.path.join(database_client.path, str(collection_client.instance.id)))) self.assertTrue(os.path.exists(os.path.join(collection_client.path, 'file1.txt'))) @@ -342,7 +344,7 @@ def test_new_collection_with_files_relative_to(self): items=collection_files, relative_to=os.path.join(root_dir, 'files') ) - new_file_collection = self.session.query(FileCollection).get(collection_client.instance.id) + new_file_collection = self.session.get(FileCollection, collection_client.instance.id) self.assertTrue(new_file_collection is not None) self.assertTrue(os.path.exists(os.path.join(database_client.path, str(collection_client.instance.id)))) self.assertTrue(os.path.exists(os.path.join(collection_client.path, 'file1.txt'))) @@ -367,7 +369,7 @@ def test_new_collection_with_meta(self): ) database_client = FileDatabaseClient(self.session, root_dir, database_id) collection_client = database_client.new_collection(meta={'Key1': 'Val1', 'Key2': 'Val2'}) - new_file_collection = self.session.query(FileCollection).get(collection_client.instance.id) + new_file_collection = self.session.get(FileCollection, collection_client.instance.id) self.assertTrue(new_file_collection is not None) self.assertDictEqual(collection_client.instance.meta, {'Key1': 'Val1', 'Key2': 'Val2'}) @@ -385,7 +387,7 @@ def test_new_collection_fail_no_commit(self, mock_new_collection): database_meta={'Key1': 'StringValue', 'Key2': 1234, 'Key3': 1.23} ) database_client = FileDatabaseClient(self.session, root_dir, database_id) - pre_count = self.session.query(FileCollection).count() + pre_count = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() collection_files = [ os.path.join(root_dir, 'files', 'file1.txt'), os.path.join(root_dir, 'files', 'dir1'), @@ -393,7 +395,7 @@ def test_new_collection_fail_no_commit(self, mock_new_collection): mock_new_collection.side_effect = FileNotFoundError('Mock Exception') with self.assertRaises(FileNotFoundError): _ = database_client.new_collection(items=collection_files) - post_count = self.session.query(FileCollection).count() + post_count = self.session.execute(select(func.count()).select_from(FileCollection)).scalar() self.assertEqual(pre_count, post_count) def test_get_collection(self): @@ -595,21 +597,26 @@ def test_delete_collection(self): collection_path = collection_client.path database_client.delete_collection(collection_id) self.assertTrue( - self.session.query(FileCollection).filter_by(id=collection_id, file_database_id=database_id).count() == 0 + self.session.execute( + select(func.count()).select_from(FileCollection).where( + FileCollection.id == collection_id, + FileCollection.file_database_id == database_id, + ) + ).scalar() == 0 ) self.assertFalse(os.path.exists(collection_path)) def test_delete(self): """Test deleting the file database.""" - self.assertTrue(self.session.query(FileDatabase).count() == 0) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileDatabase)).scalar() == 0) root_dir = os.path.join(self.test_files_base, 'temp', 'test_new_file_database_client') database_client = FileDatabaseClient.new(self.session, root_dir) - self.assertTrue(self.session.query(FileDatabase).count() == 1) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileDatabase)).scalar() == 1) self.assertTrue(os.path.exists(database_client.path)) database_client.delete() - self.assertTrue(self.session.query(FileDatabase).count() == 0) + self.assertTrue(self.session.execute(select(func.count()).select_from(FileDatabase)).scalar() == 0) self.assertFalse(os.path.exists(database_client.path)) def test_get_delete_does_not_exist(self): diff --git a/tethysext/atcore/tests/integrated_tests/services/model_database.py b/tethysext/atcore/tests/integrated_tests/services/model_database.py index 378d8bed..db439b85 100644 --- a/tethysext/atcore/tests/integrated_tests/services/model_database.py +++ b/tethysext/atcore/tests/integrated_tests/services/model_database.py @@ -9,6 +9,7 @@ import unittest from unittest import mock import sqlalchemy +from sqlalchemy.engine import make_url from tethys_sdk.base import TethysAppBase from tethysext.atcore.services.model_database import ModelDatabase from tethysext.atcore.services.model_database_connection import ModelDatabaseConnection @@ -53,6 +54,22 @@ def close(self): pass +class MockConnection(object): + """Context-managed connection backed by a MockEngine.""" + + def __init__(self, engine): + self._engine = engine + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return False + + def execute(self, statement): + return self._engine._execute_sql(str(statement)) + + class MockEngine(object): def __init__(self, connection_name): @@ -61,7 +78,10 @@ def __init__(self, connection_name): def dispose(self): pass - def execute(self, query): + def connect(self): + return MockConnection(self) + + def _execute_sql(self, query): """ Returns different values for different queries. CONN_1 and CONN_2 return different count and size. @@ -110,7 +130,7 @@ def mock_get_engine(connection_name, as_url=False): return None if as_url: - return 'postgresql://name:pass@localhost:5435/{}_{}'.format('foo', connection_name) + return make_url('postgresql://name:pass@localhost:5435/{}_{}'.format('foo', connection_name)) return MockEngine(connection_name) diff --git a/tethysext/atcore/tests/integrated_tests/services/resource_condor_workflow_tests.py b/tethysext/atcore/tests/integrated_tests/services/resource_condor_workflow_tests.py index beabc391..a0e48d09 100644 --- a/tethysext/atcore/tests/integrated_tests/services/resource_condor_workflow_tests.py +++ b/tethysext/atcore/tests/integrated_tests/services/resource_condor_workflow_tests.py @@ -51,7 +51,7 @@ def tearDown(self): @mock.patch('tethysext.atcore.services.resource_condor_workflow.sessionmaker') def test_run_job(self, mock_sessionmaker, _): mock_session = mock_sessionmaker()() - mock_resource = mock_session.query().get() + mock_resource = mock_session.get() self.puw.prepare = mock.MagicMock() self.puw.workflow = mock.MagicMock() self.puw.run_job() diff --git a/tethysext/atcore/tests/integrated_tests/services/spatial_reference.py b/tethysext/atcore/tests/integrated_tests/services/spatial_reference.py index 09b30be8..27f4d155 100644 --- a/tethysext/atcore/tests/integrated_tests/services/spatial_reference.py +++ b/tethysext/atcore/tests/integrated_tests/services/spatial_reference.py @@ -7,6 +7,7 @@ ******************************************************************************** """ import unittest +from sqlalchemy import text from sqlalchemy.engine import create_engine from tethysext.atcore.tests import TEST_DB_URL from tethysext.atcore.services.spatial_reference import SpatialReferenceService @@ -17,7 +18,8 @@ def setUpModule(): # Connect to the database and create the schema within a transaction engine = create_engine(TEST_DB_URL) - engine.execute('CREATE EXTENSION IF NOT EXISTS postgis;') + with engine.begin() as connection: + connection.execute(text('CREATE EXTENSION IF NOT EXISTS postgis;')) def tearDownModule(): diff --git a/tethysext/atcore/tests/unit_tests/services/model_db_spatial_manager.py b/tethysext/atcore/tests/unit_tests/services/model_db_spatial_manager.py index 57237cdf..d2bf5f4b 100644 --- a/tethysext/atcore/tests/unit_tests/services/model_db_spatial_manager.py +++ b/tethysext/atcore/tests/unit_tests/services/model_db_spatial_manager.py @@ -38,7 +38,7 @@ def test_get_projection_units_ft(self): proj4text="+proj=utm +zone=20 +datum=WGS84 +units=ft +no_defs " ) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -51,7 +51,7 @@ def test_get_projection_units_m(self): proj4text="+proj=utm +zone=20 +datum=WGS84 +units=m +no_defs " ) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -62,7 +62,7 @@ def test_get_projection_units_no_units(self): srid = 2232 mock_row = mock.MagicMock(proj4text="+proj=utm +zone=20 +datum=WGS84 +no_defs ") mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -79,7 +79,7 @@ def test_get_projection_units_unknown_units(self): proj4text="+proj=utm +zone=20 +datum=WGS84 +units=teva +no_defs " ) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -95,23 +95,23 @@ def test_get_projection_string(self): mock_project_string = "FAKE PROJECTION STRING" mock_row = mock.MagicMock(proj_string=mock_project_string) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) ret = model_db_spatial_manager.get_projection_string(mock_model_db, srid) model_db_spatial_manager.get_projection_string(mock_model_db, srid) - execute_calls = mock_engine.execute.call_args_list + execute_calls = mock_engine.connect.return_value.__enter__.return_value.execute.call_args_list self.assertEqual(mock_project_string, ret) self.assertEqual(1, len(execute_calls)) - self.assertIn("srtext", execute_calls[0][0][0]) + self.assertIn("srtext", str(execute_calls[0][0][0])) def test_get_projection_string_wkt(self): srid = 2232 mock_project_string = "FAKE PROJECTION STRING" mock_row = mock.MagicMock(proj_string=mock_project_string) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -121,17 +121,17 @@ def test_get_projection_string_wkt(self): model_db_spatial_manager.get_projection_string( mock_model_db, srid, ModelDBSpatialManager.PRO_WKT ) - execute_calls = mock_engine.execute.call_args_list + execute_calls = mock_engine.connect.return_value.__enter__.return_value.execute.call_args_list self.assertEqual(mock_project_string, ret) self.assertEqual(1, len(execute_calls)) - self.assertIn("srtext", execute_calls[0][0][0]) + self.assertIn("srtext", str(execute_calls[0][0][0])) def test_get_projection_string_proj4(self): srid = 2232 mock_project_string = "FAKE PROJECTION STRING" mock_row = mock.MagicMock(proj_string=mock_project_string) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -141,17 +141,17 @@ def test_get_projection_string_proj4(self): model_db_spatial_manager.get_projection_string( mock_model_db, srid, ModelDBSpatialManager.PRO_PROJ4 ) - execute_calls = mock_engine.execute.call_args_list + execute_calls = mock_engine.connect.return_value.__enter__.return_value.execute.call_args_list self.assertEqual(mock_project_string, ret) self.assertEqual(1, len(execute_calls)) - self.assertIn("proj4text", execute_calls[0][0][0]) + self.assertIn("proj4text", str(execute_calls[0][0][0])) def test_get_projection_string_invalid(self): srid = 2232 mock_project_string = "FAKE PROJECTION STRING" mock_row = mock.MagicMock(proj_string=mock_project_string) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -168,7 +168,7 @@ def test_get_projection_string_same_srid_different_format(self): mock_project_string = "FAKE PROJECTION STRING" mock_row = mock.MagicMock(proj_string=mock_project_string) mock_engine = mock.MagicMock() - mock_engine.execute.return_value = [mock_row] + mock_engine.connect.return_value.__enter__.return_value.execute.return_value = [mock_row] mock_model_db = mock.MagicMock() mock_model_db.get_engine.return_value = mock_engine model_db_spatial_manager = ModelDBSpatialManager(self.geoserver_engine) @@ -184,11 +184,11 @@ def test_get_projection_string_same_srid_different_format(self): model_db_spatial_manager.get_projection_string( mock_model_db, srid, ModelDBSpatialManager.PRO_WKT ) - execute_calls = mock_engine.execute.call_args_list + execute_calls = mock_engine.connect.return_value.__enter__.return_value.execute.call_args_list self.assertEqual(mock_project_string, ret) self.assertEqual(2, len(execute_calls)) - self.assertIn("srtext", execute_calls[0][0][0]) - self.assertIn("proj4text", execute_calls[1][0][0]) + self.assertIn("srtext", str(execute_calls[0][0][0])) + self.assertIn("proj4text", str(execute_calls[1][0][0])) def test_link_geoserver_to_db_store_exists(self): model_db_spatial_manager = _ModelDBSpatialManager(self.geoserver_engine) diff --git a/tethysext/atcore/urls/spatial_reference.py b/tethysext/atcore/urls/spatial_reference.py index d9afd352..4a158696 100644 --- a/tethysext/atcore/urls/spatial_reference.py +++ b/tethysext/atcore/urls/spatial_reference.py @@ -14,20 +14,14 @@ def urls( custom_services=(), ): """ - Generate UrlMap objects for spatial_reference_select gizmo. - - :: - - {% url 'my_first_app:app_users_add_user %} - {% url 'my_first_app:app_users_edit_user, user_id=user.id %} + Generate UrlMap objects for spatial reference REST endpoints. Args: url_map_maker(UrlMap): UrlMap class bound to app root url. app(TethysAppBase): instance of Tethys app class. persistent_store_name(str): name of persistent store database setting the controllers should use to create sessions. - base_template(str): relative path to base template (e.g.: 'my_first_app/base.html'). Useful to add navigation to ManageUsers, ManageOrganizations, ManageResources, and UserAccount views. custom_controllers(list<TethysController>): Any number of TethysController subclasses to override default controller classes. - custom_services(cls): custom subclasses of SpatialRefereceService service. + custom_services(cls): custom subclasses of SpatialReferenceService service. Url Map Names: atcore_query_spatial_reference diff --git a/tethysext/atcore/utilities.py b/tethysext/atcore/utilities.py index 79deb408..b86c1212 100644 --- a/tethysext/atcore/utilities.py +++ b/tethysext/atcore/utilities.py @@ -115,7 +115,7 @@ def clean_request(request): def strip_list(the_list, *args): """ - Strip emtpy items from end of list. + Strip empty items from end of list. Args: the_list(list): the list. diff --git a/tox.ini b/tox.ini index cf2a25f7..b9b27081 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,3 @@ [flake8] max-line-length = 120 -exclude = .git,build,dist,__pycache__,.eggs,*.egg-info +exclude = .git,build,dist,__pycache__,.eggs,*.egg-info,.venv diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 00000000..4bbd4fe6 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production build +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/website/README.md b/website/README.md new file mode 100644 index 00000000..fd3a054d --- /dev/null +++ b/website/README.md @@ -0,0 +1,38 @@ +# tethysext-atcore documentation site + +This directory contains the [Docusaurus 3.x](https://docusaurus.io/) site that +publishes the `tethysext-atcore` documentation to GitHub Pages. + +## Local development + +```bash +cd website +npm install +npm start +``` + +`npm start` launches a local dev server with hot reload at +http://localhost:3000/tethysext-atcore/. + +## Production build + +```bash +npm run build +npm run serve # optional: preview the static build +``` + +The static site is emitted to `website/build/`. + +## Deployment + +Deployment is automated. On every push to `master` that touches +`website/**` or `tethysext/**`, the `.github/workflows/docs.yml` workflow +builds the site and publishes it to GitHub Pages via +`actions/deploy-pages`. + +## Authoring + +- **Narrative content** lives directly under `website/docs/`. The + `docs-narrative-writer` agent is responsible for these pages. +- **API reference** lives under `website/docs/api/` and is generated as + MDX by the `docs-api-writer` agent. Do not hand-edit that directory. diff --git a/website/docs/api/_category_.json b/website/docs/api/_category_.json new file mode 100644 index 00000000..ec4123c5 --- /dev/null +++ b/website/docs/api/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "API Reference", + "position": 99 +} diff --git a/website/docs/api/cli/_category_.json b/website/docs/api/cli/_category_.json new file mode 100644 index 00000000..d09c5166 --- /dev/null +++ b/website/docs/api/cli/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "cli", + "position": 2 +} diff --git a/website/docs/api/cli/cli_helpers.mdx b/website/docs/api/cli/cli_helpers.mdx new file mode 100644 index 00000000..55a632fc --- /dev/null +++ b/website/docs/api/cli/cli_helpers.mdx @@ -0,0 +1,34 @@ +--- +id: cli.cli_helpers +title: tethysext.atcore.cli.cli_helpers +sidebar_label: cli_helpers +--- + +# `tethysext.atcore.cli.cli_helpers` + +> _No description._ + +## Functions + + +### `print_error(statement)` \{#print-error\} + +> _No description._ + + + +### `print_success(statement)` \{#print-success\} + +> _No description._ + + + +### `print_info(statement)` \{#print-info\} + +> _No description._ + + + +### `print_header(statement)` \{#print-header\} + +> _No description._ diff --git a/website/docs/api/cli/index.mdx b/website/docs/api/cli/index.mdx new file mode 100644 index 00000000..6bcd6e5a --- /dev/null +++ b/website/docs/api/cli/index.mdx @@ -0,0 +1,30 @@ +--- +id: cli.index +title: tethysext.atcore.cli +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.cli` + +```text +******************************************************************************** +* Name: __init__.py +* Author: Michael Souffront and Tran Hoang +* Created On: Oct 9, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Functions + + +### `atcore_command()` \{#atcore-command\} + +```text +atcore commandline interface function. +``` + +## Modules + +- [`cli_helpers`](./cli_helpers.mdx) +- [`init_command`](./init_command.mdx) diff --git a/website/docs/api/cli/init_command.mdx b/website/docs/api/cli/init_command.mdx new file mode 100644 index 00000000..01914e81 --- /dev/null +++ b/website/docs/api/cli/init_command.mdx @@ -0,0 +1,18 @@ +--- +id: cli.init_command +title: tethysext.atcore.cli.init_command +sidebar_label: init_command +--- + +# `tethysext.atcore.cli.init_command` + +> _No description._ + +## Functions + + +### `init_atcore(arguments)` \{#init-atcore\} + +```text +Commandline interface for initializing the atcore ext. +``` diff --git a/website/docs/api/controllers/_category_.json b/website/docs/api/controllers/_category_.json new file mode 100644 index 00000000..d2b51e34 --- /dev/null +++ b/website/docs/api/controllers/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "controllers", + "position": 3 +} diff --git a/website/docs/api/controllers/app_users/_category_.json b/website/docs/api/controllers/app_users/_category_.json new file mode 100644 index 00000000..5444776b --- /dev/null +++ b/website/docs/api/controllers/app_users/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "app_users", + "position": 4 +} diff --git a/website/docs/api/controllers/app_users/add_existing_user.mdx b/website/docs/api/controllers/app_users/add_existing_user.mdx new file mode 100644 index 00000000..81bc4a42 --- /dev/null +++ b/website/docs/api/controllers/app_users/add_existing_user.mdx @@ -0,0 +1,36 @@ +--- +id: controllers.app_users.add_existing_user +title: tethysext.atcore.controllers.app_users.add_existing_user +sidebar_label: add_existing_user +--- + +# `tethysext.atcore.controllers.app_users.add_existing_user` + +> _No description._ + +## Classes + + +### `AddExistingUser(AppUsersViewMixin)` \{#addexistinguser\} + +```text +Controller for add_existing_user page. + +GET: Render form for adding an existing user from the Django user database. +POST: Handle form submission to add an existing a new user from the Django user database. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#addexistinguser-get\} + +```text +Route get requests. +``` + + +##### `post(self, request, *args, **kwargs)` \{#addexistinguser-post\} + +```text +Route post requests. +``` diff --git a/website/docs/api/controllers/app_users/index.mdx b/website/docs/api/controllers/app_users/index.mdx new file mode 100644 index 00000000..3a50090e --- /dev/null +++ b/website/docs/api/controllers/app_users/index.mdx @@ -0,0 +1,25 @@ +--- +id: controllers.app_users.index +title: tethysext.atcore.controllers.app_users +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.app_users` + +> _No description._ + +## Modules + +- [`add_existing_user`](./add_existing_user.mdx) +- [`manage_organization_members`](./manage_organization_members.mdx) +- [`manage_organizations`](./manage_organizations.mdx) +- [`manage_resources`](./manage_resources.mdx) +- [`manage_users`](./manage_users.mdx) +- [`mixins`](./mixins.mdx) +- [`modify_organization`](./modify_organization.mdx) +- [`modify_resource`](./modify_resource.mdx) +- [`modify_user`](./modify_user.mdx) +- [`resource_details`](./resource_details.mdx) +- [`resource_status`](./resource_status.mdx) +- [`user_account`](./user_account.mdx) diff --git a/website/docs/api/controllers/app_users/manage_organization_members.mdx b/website/docs/api/controllers/app_users/manage_organization_members.mdx new file mode 100644 index 00000000..a510c8ae --- /dev/null +++ b/website/docs/api/controllers/app_users/manage_organization_members.mdx @@ -0,0 +1,42 @@ +--- +id: controllers.app_users.manage_organization_members +title: tethysext.atcore.controllers.app_users.manage_organization_members +sidebar_label: manage_organization_members +--- + +# `tethysext.atcore.controllers.app_users.manage_organization_members` + +```text +******************************************************************************** +* Name: manage_organization_members +* Author: nswain +* Created On: April 03, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ManageOrganizationMembers(AppUsersViewMixin)` \{#manageorganizationmembers\} + +```text +Controller for manage_organization_members page. + +GET: Render form for adding/editing user. +POST: Handle form submission to add/edit a new user. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#manageorganizationmembers-get\} + +```text +Route get requests. +``` + + +##### `post(self, request, *args, **kwargs)` \{#manageorganizationmembers-post\} + +```text +Route post requests. +``` diff --git a/website/docs/api/controllers/app_users/manage_organizations.mdx b/website/docs/api/controllers/app_users/manage_organizations.mdx new file mode 100644 index 00000000..5abc1e09 --- /dev/null +++ b/website/docs/api/controllers/app_users/manage_organizations.mdx @@ -0,0 +1,67 @@ +--- +id: controllers.app_users.manage_organizations +title: tethysext.atcore.controllers.app_users.manage_organizations +sidebar_label: manage_organizations +--- + +# `tethysext.atcore.controllers.app_users.manage_organizations` + +```text +******************************************************************************** +* Name: manage_organizations +* Author: nswain +* Created On: April 03, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ManageOrganizations(MultipleResourcesViewMixin)` \{#manageorganizations\} + +```text +Controller for manage_organizations page. + +GET: Render list of all organizations. +DELETE: Delete an organization. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#manageorganizations-get\} + +```text +Route get requests. +``` + + +##### `delete(self, request, *args, **kwargs)` \{#manageorganizations-delete\} + +```text +Route delete requests. +``` + + +##### `add_custom_fields(self, organization, organization_card)` \{#manageorganizations-add-custom-fields\} + +```text +Hook to add custom fields to each organization card. +Args: + organization(Organization): the sqlalchemy Organization instance. + organization_card(dict): the default organization card. +Returns: + dict: customized organization card. +``` + + +##### `perform_custom_delete_operations(self, request, organization)` \{#manageorganizations-perform-custom-delete-operations\} + +```text +Hook to perform custom delete operations prior to the organization being deleted. +Args: + request(django.Request): the DELETE request object. + organization(Organization): the sqlalchemy Organization instance to be deleted. + +Raises: + Exception: raise an appropriate exception if an error occurs. The message will be sent as the 'error' field of the JsonResponse. +``` diff --git a/website/docs/api/controllers/app_users/manage_resources.mdx b/website/docs/api/controllers/app_users/manage_resources.mdx new file mode 100644 index 00000000..a356d19b --- /dev/null +++ b/website/docs/api/controllers/app_users/manage_resources.mdx @@ -0,0 +1,147 @@ +--- +id: controllers.app_users.manage_resources +title: tethysext.atcore.controllers.app_users.manage_resources +sidebar_label: manage_resources +--- + +# `tethysext.atcore.controllers.app_users.manage_resources` + +```text +******************************************************************************** +* Name: manage_resources.py +* Author: nswain +* Created On: April 18, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ManageResources(ResourceViewMixin)` \{#manageresources\} + +```text +Controller for manage_resources page. + +GET: Render list of all resources. +DELETE: Delete and organization. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#manageresources-get\} + +```text +Route get requests. +``` + + +##### `post(self, request, *args, **kwargs)` \{#manageresources-post\} + +```text +Route post requests. +``` + + +##### `delete(self, request, *args, **kwargs)` \{#manageresources-delete\} + +```text +Route delete requests. +``` + + +##### `get_working_url(self, request, resource)` \{#manageresources-get-working-url\} + +```text +Get the URL for the Resource Working button. +``` + + +##### `get_launch_url(self, request, resource)` \{#manageresources-get-launch-url\} + +```text +Get the URL for the Resource Launch button. +``` + + +##### `get_error_url(self, request, resource)` \{#manageresources-get-error-url\} + +```text +Get the URL for the Resource Error button. +``` + + +##### `get_info_url(self, request, resource)` \{#manageresources-get-info-url\} + +```text +Get the URL for the Resource name link and row click. +``` + + +##### `get_resource_action(self, session, request, request_app_user, resource)` \{#manageresources-get-resource-action\} + +```text +Get the parameters that define the action button (i.e.: Launch button). + +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + +Returns: + dict<action, title, href>: action attributes. +``` + + +##### `get_resources(self, session, request, request_app_user)` \{#manageresources-get-resources\} + +```text +Hook to allow easy customization of the resources query. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. +Returns: + list<Resources>: the list of resources to render on the manage_resources page. +``` + + +##### `perform_custom_delete_operations(self, session, request, resource)` \{#manageresources-perform-custom-delete-operations\} + +```text +Hook to perform custom delete operations prior to the resource being deleted. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.Request): the DELETE request object. + resource(Resource): the sqlalchemy Resource instance to be deleted. + +Raises: + Exception: raise an appropriate exception if an error occurs. The message will be sent as the 'error' field of the JsonResponse. +``` + + +##### `can_edit_resource(self, session, request, resource)` \{#manageresources-can-edit-resource\} + +```text +Hook into resource_card.editable attribute to allow for more than permissions-based check. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.Request): the request object. + resource(Resource): current resource. + +Returns: + bool: the edit button will be displayed for this resource if True. +``` + + +##### `can_delete_resource(self, session, request, resource)` \{#manageresources-can-delete-resource\} + +```text +Hook into resource_card.deletable attribute to allow for more than permissions-based check. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.Request): the request object. + resource(Resource): current resource. + +Returns: + bool: the delete button will be displayed for this resource if True. +``` diff --git a/website/docs/api/controllers/app_users/manage_users.mdx b/website/docs/api/controllers/app_users/manage_users.mdx new file mode 100644 index 00000000..5d4ec562 --- /dev/null +++ b/website/docs/api/controllers/app_users/manage_users.mdx @@ -0,0 +1,42 @@ +--- +id: controllers.app_users.manage_users +title: tethysext.atcore.controllers.app_users.manage_users +sidebar_label: manage_users +--- + +# `tethysext.atcore.controllers.app_users.manage_users` + +```text +******************************************************************************** +* Name: users.py +* Author: nswain +* Created On: March 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ManageUsers(AppUsersViewMixin)` \{#manageusers\} + +```text +Controller for manage_users page. + +GET: Render list of all users. +DELETE: Delete/remove user. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#manageusers-get\} + +```text +Route get requests. +``` + + +##### `delete(self, request, *args, **kwargs)` \{#manageusers-delete\} + +```text +Route delete requests. +``` diff --git a/website/docs/api/controllers/app_users/mixins.mdx b/website/docs/api/controllers/app_users/mixins.mdx new file mode 100644 index 00000000..8fd56d12 --- /dev/null +++ b/website/docs/api/controllers/app_users/mixins.mdx @@ -0,0 +1,161 @@ +--- +id: controllers.app_users.mixins +title: tethysext.atcore.controllers.app_users.mixins +sidebar_label: mixins +--- + +# `tethysext.atcore.controllers.app_users.mixins` + +```text +******************************************************************************** +* Name: base.py +* Author: nswain +* Created On: April 06, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `AppUsersViewMixin(TethysController)` \{#appusersviewmixin\} + +```text +Mixin for class-based views that adds convenience methods for working with the app user models. +``` +#### Methods + + +##### `get_app(self)` \{#appusersviewmixin-get-app\} + +> _No description._ + + + +##### `get_app_user_model(self)` \{#appusersviewmixin-get-app-user-model\} + +> _No description._ + + + +##### `get_organization_model(self)` \{#appusersviewmixin-get-organization-model\} + +> _No description._ + + + +##### `get_permissions_manager(self)` \{#appusersviewmixin-get-permissions-manager\} + +> _No description._ + + + +##### `get_sessionmaker(self)` \{#appusersviewmixin-get-sessionmaker\} + +> _No description._ + + + +##### `get_base_context(self, request)` \{#appusersviewmixin-get-base-context\} + +> _No description._ + + + + +### `ResourceBackUrlViewMixin(AppUsersViewMixin)` \{#resourcebackurlviewmixin\} + +> _No description._ + +#### Methods + + +##### `get_resource(self, request, resource_id, session=None)` \{#resourcebackurlviewmixin-get-resource\} + +```text +Get the resource and check permissions. + +Args: + request: Django HttpRequest. + resource_id: ID of the resource. + session: SQLAlchemy session. Optional. + +Returns: + Resource: the resource. +``` + + +##### `dispatch(self, request, *args, **kwargs)` \{#resourcebackurlviewmixin-dispatch\} + +```text +Intercept kwargs before calling handler method. +``` + + +##### `default_back_url(self, request, *args, **kwargs)` \{#resourcebackurlviewmixin-default-back-url\} + +```text +Hook for custom back url. Defaults to the resource details page. + +Returns: + str: back url. +``` + + + +### `ResourceViewMixin(ResourceBackUrlViewMixin)` \{#resourceviewmixin\} + +```text +Mixin for class-based views that adds convenience methods for working with resources. +``` +#### Methods + + +##### `get_resource_model(self)` \{#resourceviewmixin-get-resource-model\} + +> _No description._ + + + +##### `get_resource(self, request, resource_id, session=None)` \{#resourceviewmixin-get-resource\} + +```text +Get the resource and check permissions. + +Args: + request: Django HttpRequest. + resource_id: ID of the resource. + session: SQLAlchemy session. Optional. + +Returns: + Resource: the resource. +``` + + + +### `MultipleResourcesViewMixin(ResourceBackUrlViewMixin)` \{#multipleresourcesviewmixin\} + +```text +Mixin for class-based views that adds convenience methods for working with resources. +``` +#### Methods + + +##### `get_resource_models(self)` \{#multipleresourcesviewmixin-get-resource-models\} + +> _No description._ + + + +##### `get_resource(self, request, resource_id, session=None)` \{#multipleresourcesviewmixin-get-resource\} + +```text +Get the resource and check permissions. + +Args: + request: Django HttpRequest. + resource_id: ID of the resource. + session: SQLAlchemy session. Optional. + +Returns: + Resource: the resource. +``` diff --git a/website/docs/api/controllers/app_users/modify_organization.mdx b/website/docs/api/controllers/app_users/modify_organization.mdx new file mode 100644 index 00000000..9d2e65e9 --- /dev/null +++ b/website/docs/api/controllers/app_users/modify_organization.mdx @@ -0,0 +1,109 @@ +--- +id: controllers.app_users.modify_organization +title: tethysext.atcore.controllers.app_users.modify_organization +sidebar_label: modify_organization +--- + +# `tethysext.atcore.controllers.app_users.modify_organization` + +```text +******************************************************************************** +* Name: modify_organization +* Author: nswain +* Created On: April 03, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModifyOrganization(MultipleResourcesViewMixin)` \{#modifyorganization\} + +```text +Controller for modify_organization page. + +GET: Render form for adding/editing user. +POST: Handle form submission to add/edit a new user. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#modifyorganization-get\} + +```text +Route get requests. +``` + + +##### `post(self, request, *args, **kwargs)` \{#modifyorganization-post\} + +```text +Route post requests. +``` + + +##### `get_license_to_consultant_map(self, request, license_options, consultant_organizations)` \{#modifyorganization-get-license-to-consultant-map\} + +```text +Build map of organizations that can still add clients for each of the license options. +Args: + request(django.request): Django request object + license_options(list): List of license tuples e.g.: [('Standard': 'standard'), ('Advanced', 'advanced')] + consultant_organizations(list<Organization>): list of consultant organitions. +Returns: + dict: liceses as keys, and a list of organizations that can add clients of that license as values. +``` + + +##### `get_hide_consultant_licenses(self, request)` \{#modifyorganization-get-hide-consultant-licenses\} + +```text +Get a list of licenses that will cause the consultant field to be hidden/disabled. +Args: + request(django.request): Django request object + +Returns: + list: List of licenses will cause the consultant field to be hidden/disabled. +``` + + +##### `initialize_custom_fields(self, session, request, organization, editing)` \{#modifyorganization-initialize-custom-fields\} + +```text +Hook to allow for initializing custom fields. + +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + organization(Organization): The organization being created / edited. + editing(bool): True if rendering form for editing. + +Returns: + dict: Template context variables for defining custom fields (i.e. gizmos, initial values, etc.). +``` + + +##### `validate_custom_fields(self, params)` \{#modifyorganization-validate-custom-fields\} + +```text +Hook to allow for validating custom fields. + +Args: + params: The request.POST object with values submitted by user. + +Returns: + bool, dict: False if any custom fields invalid, Template context variables for validation feedback (i.e. error messages). +``` + + +##### `handle_organization_finished_processing(self, session, request, request_app_user, organization, editing)` \{#modifyorganization-handle-organization-finished-processing\} + +```text +Hook to allow for post processing after the resource has finished being created or updated. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + organization(Organization): The organization being created / edited. + editing(bool): True if editing, False if creating a new resource. +``` diff --git a/website/docs/api/controllers/app_users/modify_resource.mdx b/website/docs/api/controllers/app_users/modify_resource.mdx new file mode 100644 index 00000000..d3a38fdf --- /dev/null +++ b/website/docs/api/controllers/app_users/modify_resource.mdx @@ -0,0 +1,188 @@ +--- +id: controllers.app_users.modify_resource +title: tethysext.atcore.controllers.app_users.modify_resource +sidebar_label: modify_resource +--- + +# `tethysext.atcore.controllers.app_users.modify_resource` + +```text +******************************************************************************** +* Name: modify_resource.py +* Author: nswain +* Created On: April 18, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModifyResource(ResourceViewMixin)` \{#modifyresource\} + +```text +Controller for modify_resource page. + +GET: Render form for adding/editing resource. +POST: Handle form submission to add/edit a new resource. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#modifyresource-get\} + +```text +Route get requests. +``` + + +##### `post(self, request, *args, **kwargs)` \{#modifyresource-post\} + +```text +Route post requests. +``` + + +##### `can_create_resource(self, session, request, request_app_user)` \{#modifyresource-can-create-resource\} + +```text +Check performed when determining if a new resource can be created, considering permissions and other factors (i.e. storage). # noqa: E501 +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + +Returns: + bool: True if the request app users is able to created a new resource, else false. + str: Error message to display if False. +``` + + +##### `can_edit_resource(self, session, request, request_app_user, resource)` \{#modifyresource-can-edit-resource\} + +```text +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + resource(Resource): The resource being edited. + +Returns: + bool: True if can edit, else false. +``` + + +##### `handle_file_upload(self, session, request, request_app_user, files, resource)` \{#modifyresource-handle-file-upload\} + +```text +Handle file uploads. Raise an ATCoreException if issue occur. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + files(request.FILES): Django in-memory files object. + resource(Resource): The newly created resource. +``` + + +##### `get_parents_select_options(self, session, request, request_app_user, resource, app_user_organizations)` \{#modifyresource-get-parents-select-options\} + +```text +Build the list of options for the parent relationship select field. + +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + resource(Resource): The resource being edited. + app_user_organizations (list<str>): List of organization ids to which the app user belongs. + +Returns: + list<2-tuples<name, id>>: A list of 2-tuples, each tuple containing the name and id of a resource. +``` + + +##### `get_child_select_options(self, session, request, request_app_user, resource, app_user_organizations)` \{#modifyresource-get-child-select-options\} + +```text +Build the list of options for the child relationship select field. + +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + resource(Resource): The resource being edited. + app_user_organizations (list<str>): List of organization ids to which the app user belongs. + +Returns: + list<2-tuples<name, id>>: A list of 2-tuples, each tuple containing the name and id of a resource. +``` + + +##### `handle_srid_changed(self, session, request, request_app_user, resource, old_srid, new_srid)` \{#modifyresource-handle-srid-changed\} + +```text +Handle srid changed event when editing an existing resource. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + resource(Resource): The resource being edited. + old_srid(str): The old srid. + new_srid(str): The new srid. +``` + + +##### `handle_resource_finished_processing(self, session, request, request_app_user, resource, editing, context=None)` \{#modifyresource-handle-resource-finished-processing\} + +```text +Hook to allow for post processing after the resource has finished being created or updated. +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + resource(Resource): The resource being edited or newly created. + editing(bool): True if editing, False if creating a new resource. + contex(dict): Template context variables for the view. +``` + + +##### `initialize_custom_fields(self, session, request, resource, editing, context=None)` \{#modifyresource-initialize-custom-fields\} + +```text +Hook to allow for initializing custom fields. + +Args: + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + resource(Resource): The resource being edited. + editing(bool): True if rendering form for editing. + context(dict): Template context variables for the view. + +Returns: + dict: Template context variables for defining custom fields. +``` + + +##### `validate_custom_fields(self, params, session=None, request=None, request_app_user=None)` \{#modifyresource-validate-custom-fields\} + +```text +Hook to allow for validating custom fields. + +Args: + params: The request.POST object with values submitted by user. + session(sqlalchemy.session): open sqlalchemy session. + request(django.request): the Django request. + request_app_user(AppUser): app user that is making the request. + +Returns: + bool, dict: False if any custom fields invalid, Template context variables for validation feedback (i.e. error messages). +``` + + +##### `get_context(self, request, context, editing)` \{#modifyresource-get-context\} + +```text +Hook to add to context. +Args: + context(dict): context for controller. +``` diff --git a/website/docs/api/controllers/app_users/modify_user.mdx b/website/docs/api/controllers/app_users/modify_user.mdx new file mode 100644 index 00000000..32754dd8 --- /dev/null +++ b/website/docs/api/controllers/app_users/modify_user.mdx @@ -0,0 +1,36 @@ +--- +id: controllers.app_users.modify_user +title: tethysext.atcore.controllers.app_users.modify_user +sidebar_label: modify_user +--- + +# `tethysext.atcore.controllers.app_users.modify_user` + +> _No description._ + +## Classes + + +### `ModifyUser(AppUsersViewMixin)` \{#modifyuser\} + +```text +Controller for modify_user page. + +GET: Render form for adding/editing user. +POST: Handle form submission to add/edit a new user. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#modifyuser-get\} + +```text +Route get requests. +``` + + +##### `post(self, request, *args, **kwargs)` \{#modifyuser-post\} + +```text +Route post requests. +``` diff --git a/website/docs/api/controllers/app_users/resource_details.mdx b/website/docs/api/controllers/app_users/resource_details.mdx new file mode 100644 index 00000000..d4ac5585 --- /dev/null +++ b/website/docs/api/controllers/app_users/resource_details.mdx @@ -0,0 +1,61 @@ +--- +id: controllers.app_users.resource_details +title: tethysext.atcore.controllers.app_users.resource_details +sidebar_label: resource_details +--- + +# `tethysext.atcore.controllers.app_users.resource_details` + +```text +******************************************************************************** +* Name: resource_details.py +* Author: nswain +* Created On: April 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ResourceDetails(ResourceViewMixin)` \{#resourcedetails\} + +```text +Controller for resource_details page. + +GET: Render detail view of given resource. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#resourcedetails-get\} + +```text +Route get requests. +``` + + +##### `default_back_url(self, request, *args, **kwargs)` \{#resourcedetails-default-back-url\} + +```text +Derive the back controller. + +Args: + request: Django HttpRequest. + +Returns: + str: name of the controller to return to when hitting back or on error. +``` + + +##### `get_context(self, request, context)` \{#resourcedetails-get-context\} + +```text +Hook for modifying context. + +Args: + request(HttpRequest): Django HttpRequest. + context(dict): context object. + +Returns: + dict: context +``` diff --git a/website/docs/api/controllers/app_users/resource_status.mdx b/website/docs/api/controllers/app_users/resource_status.mdx new file mode 100644 index 00000000..5da74314 --- /dev/null +++ b/website/docs/api/controllers/app_users/resource_status.mdx @@ -0,0 +1,61 @@ +--- +id: controllers.app_users.resource_status +title: tethysext.atcore.controllers.app_users.resource_status +sidebar_label: resource_status +--- + +# `tethysext.atcore.controllers.app_users.resource_status` + +```text +******************************************************************************** +* Name: resource_status.py +* Author: nswain +* Created On: September 20, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ResourceStatus(ResourceViewMixin)` \{#resourcestatus\} + +```text +Controller for resource_status page. + +GET: Render status view of given resource. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#resourcestatus-get\} + +```text +Route get requests. +``` + + +##### `default_back_url(self, request, *args, **kwargs)` \{#resourcestatus-default-back-url\} + +```text +Derive the back controller. + +Args: + request: Django HttpRequest. + +Returns: + str: name of the controller to return to when hitting back or on error. +``` + + +##### `get_context(self, request, context)` \{#resourcestatus-get-context\} + +```text +Hook for modifying context. + +Args: + request(HttpRequest): Django HttpRequest. + context(dict): context object. + +Returns: + dict: context +``` diff --git a/website/docs/api/controllers/app_users/user_account.mdx b/website/docs/api/controllers/app_users/user_account.mdx new file mode 100644 index 00000000..b5ea8a4f --- /dev/null +++ b/website/docs/api/controllers/app_users/user_account.mdx @@ -0,0 +1,35 @@ +--- +id: controllers.app_users.user_account +title: tethysext.atcore.controllers.app_users.user_account +sidebar_label: user_account +--- + +# `tethysext.atcore.controllers.app_users.user_account` + +```text +******************************************************************************** +* Name: user_account +* Author: nswain +* Created On: April 03, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `UserAccount(AppUsersViewMixin)` \{#useraccount\} + +```text +Controller for user_account page. + +GET: Render list of all organizations. +DELETE: Delete and organization. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#useraccount-get\} + +```text +Route get requests. +``` diff --git a/website/docs/api/controllers/index.mdx b/website/docs/api/controllers/index.mdx new file mode 100644 index 00000000..489727ca --- /dev/null +++ b/website/docs/api/controllers/index.mdx @@ -0,0 +1,20 @@ +--- +id: controllers.index +title: tethysext.atcore.controllers +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers` + +> _No description._ + +## Modules + +- [`app_users`](./app_users/index.mdx) +- [`map_view`](./map_view.mdx) +- [`resource_view`](./resource_view.mdx) +- [`resource_workflows`](./resource_workflows/index.mdx) +- [`resources`](./resources/index.mdx) +- [`rest`](./rest/index.mdx) +- [`utilities`](./utilities.mdx) diff --git a/website/docs/api/controllers/map_view.mdx b/website/docs/api/controllers/map_view.mdx new file mode 100644 index 00000000..150ee95e --- /dev/null +++ b/website/docs/api/controllers/map_view.mdx @@ -0,0 +1,194 @@ +--- +id: controllers.map_view +title: tethysext.atcore.controllers.map_view +sidebar_label: map_view +--- + +# `tethysext.atcore.controllers.map_view` + +```text +******************************************************************************** +* Name: map_view.py +* Author: nswain +* Created On: October 15, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `MapView(ResourceView)` \{#mapview\} + +```text +Controller for a map view page. +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, *args, **kwargs)` \{#mapview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + +Returns: + dict: modified context dictionary. +``` + + +##### `get_permissions(self, request, permissions, resource, *args, **kwargs)` \{#mapview-get-permissions\} + +```text +Hook to modify permissions. + +Args: + request (HttpRequest): The request. + permissions (dict): The permissions dictionary with boolean values. + resource (Resource): The resource. + +Returns: + dict: modified permissions dictionary. +``` + + +##### `translate_layers_to_cesium(self, map_view_layers)` \{#mapview-translate-layers-to-cesium\} + +> _No description._ + + + +##### `save_custom_layers(self, request, session, resource, *args, **kwargs)` \{#mapview-save-custom-layers\} + +```text +Persist custom layers added to map by user. +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): The database session. + resource(Resource): The resource. + +Returns: + JsonResponse: success. +``` + + +##### `remove_custom_layer(self, request, session, resource, *args, **kwargs)` \{#mapview-remove-custom-layer\} + +```text +Remove custom layers removed by user. +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): The database session. + resource(Resource): The resource. + +Returns: + JsonResponse: success. +``` + + +##### `build_legend_item(self, request, session, resource, *args, **kwargs)` \{#mapview-build-legend-item\} + +```text +Render the HTML for a legend. +``` + + +##### `build_layer_group_tree_item(self, request, session, resource, *args, **kwargs)` \{#mapview-build-layer-group-tree-item\} + +```text +Render the HTML for a layer group tree item. + +status (create/append): create is create a whole new layer group with all the layer items associated with it + append is append an associated layer into an existing layer group +``` + + +##### `should_disable_basemap(self, request, resource, map_manager)` \{#mapview-should-disable-basemap\} + +```text +Hook to override disabling the basemap. + +Args: + request (HttpRequest): The request. + resource (Resource): Resource instance or None. + map_manager (MapManager): MapManager instance associated with this request. + +Returns: + bool: True to disable the basemap. +``` + + +##### `get_map_manager(self, request, resource, *args, **kwargs)` \{#mapview-get-map-manager\} + +```text +Lazily build and retrieve a MapManager instance. + +Args: + request (HttpRequest): The request. + resource (Resource): Resource instance or None. + +Returns: + MapManager: MapManager instance. +``` + + +##### `get_plot_data(self, request, session, resource, *args, **kwargs)` \{#mapview-get-plot-data\} + +```text +Load plot from given parameters. + +Args: + request (HttpRequest): The request. + session(sqlalchemy.Session): The database session. + resource(Resource): The resource. + +Returns: + JsonResponse: title, data, and layout options for the plot. +``` + +*`@permission_required('use_map_geocode', raise_exception=True)`* + +##### `find_location_by_query(self, request, *args, **kwargs)` \{#mapview-find-location-by-query\} + +```text +" +This controller is used in default geocode feature. + +Args: + request(HttpRequest): The request. + resource_id(str): UUID of the resource being mapped. +``` + +*`@permission_required('use_map_geocode', raise_exception=True)`* + +##### `find_location_by_advanced_query(self, request, *args, **kwargs)` \{#mapview-find-location-by-advanced-query\} + +```text +" +This controller called by the advanced geocode search feature. + +Args: + request(HttpRequest): The request. + resource_id(str): UUID of the resource being mapped. +``` + + +##### `convert_geojson_to_shapefile(self, request, session, resource, *args, **kwargs)` \{#mapview-convert-geojson-to-shapefile\} + +```text +credit to: +https://github.com/TipsForGIS/geoJSONToShpFile/blob/master/geoJ.py +Updated for pyshp 3.x compatibility. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): The database session. + resource(Resource): The resource. + +Returns: + HttpResponse: Zip file containing shapefile. +``` diff --git a/website/docs/api/controllers/resource_view.mdx b/website/docs/api/controllers/resource_view.mdx new file mode 100644 index 00000000..875e1c53 --- /dev/null +++ b/website/docs/api/controllers/resource_view.mdx @@ -0,0 +1,96 @@ +--- +id: controllers.resource_view +title: tethysext.atcore.controllers.resource_view +sidebar_label: resource_view +--- + +# `tethysext.atcore.controllers.resource_view` + +```text +******************************************************************************** +* Name: base_resource_view.py +* Author: nswain +* Created On: May 6, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `ResourceView(ResourceViewMixin)` \{#resourceview\} + +```text +Base controller for all Resource-based views. +``` +#### Methods + +*`@active_user_required()` `@resource_controller()`* + +##### `get(self, request, session, resource, back_url, *args, **kwargs)` \{#resourceview-get\} + +```text +Handle GET requests. +``` + +*`@active_user_required()` `@resource_controller()`* + +##### `post(self, request, session, resource, back_url, *args, **kwargs)` \{#resourceview-post\} + +```text +Route POST requests. +``` + + +##### `request_to_method(self, request)` \{#resourceview-request-to-method\} + +```text +Derive python method on this class from "method" GET or POST parameter. +Args: + request (HttpRequest): The request. + +Returns: + callable: the method or None if not found. +``` + + +##### `on_get(self, request, session, resource, *args, **kwargs)` \{#resourceview-on-get\} + +```text +Hook that is called at the beginning of the get request, before any other controller logic occurs. + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. +Returns: + None or HttpResponse: If an HttpResponse is returned, render that instead. +``` + + +##### `get_context(self, request, session, resource, context, *args, **kwargs)` \{#resourceview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + +Returns: + dict: modified context dictionary. +``` + + +##### `get_permissions(self, request, permissions, resource, *args, **kwargs)` \{#resourceview-get-permissions\} + +```text +Hook to modify permissions. + +Args: + request (HttpRequest): The request. + permissions (dict): The permissions dictionary with boolean values. + resource (Resource): the resource for this request. + +Returns: + dict: modified permisssions dictionary. +``` diff --git a/website/docs/api/controllers/resource_workflows/_category_.json b/website/docs/api/controllers/resource_workflows/_category_.json new file mode 100644 index 00000000..28f4eee2 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "resource_workflows", + "position": 5 +} diff --git a/website/docs/api/controllers/resource_workflows/index.mdx b/website/docs/api/controllers/resource_workflows/index.mdx new file mode 100644 index 00000000..1327f4fa --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/index.mdx @@ -0,0 +1,26 @@ +--- +id: controllers.resource_workflows.index +title: tethysext.atcore.controllers.resource_workflows +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.resource_workflows` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: November 21, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Modules + +- [`map_workflows`](./map_workflows/index.mdx) +- [`mixins`](./mixins.mdx) +- [`resource_workflow_router`](./resource_workflow_router.mdx) +- [`results_views`](./results_views/index.mdx) +- [`workflow_results_view`](./workflow_results_view.mdx) +- [`workflow_view`](./workflow_view.mdx) +- [`workflow_views`](./workflow_views/index.mdx) diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/_category_.json b/website/docs/api/controllers/resource_workflows/map_workflows/_category_.json new file mode 100644 index 00000000..31a345e6 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "map_workflows", + "position": 6 +} diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/index.mdx b/website/docs/api/controllers/resource_workflows/map_workflows/index.mdx new file mode 100644 index 00000000..178af544 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/index.mdx @@ -0,0 +1,24 @@ +--- +id: controllers.resource_workflows.map_workflows.index +title: tethysext.atcore.controllers.resource_workflows.map_workflows +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.resource_workflows.map_workflows` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: January 18, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Modules + +- [`map_workflow_view`](./map_workflow_view.mdx) +- [`spatial_condor_job_mwv`](./spatial_condor_job_mwv.mdx) +- [`spatial_data_mwv`](./spatial_data_mwv.mdx) +- [`spatial_dataset_mwv`](./spatial_dataset_mwv.mdx) +- [`spatial_input_mwv`](./spatial_input_mwv.mdx) diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/map_workflow_view.mdx b/website/docs/api/controllers/resource_workflows/map_workflows/map_workflow_view.mdx new file mode 100644 index 00000000..8fe67198 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/map_workflow_view.mdx @@ -0,0 +1,102 @@ +--- +id: controllers.resource_workflows.map_workflows.map_workflow_view +title: tethysext.atcore.controllers.resource_workflows.map_workflows.map_workflow_view +sidebar_label: map_workflow_view +--- + +# `tethysext.atcore.controllers.resource_workflows.map_workflows.map_workflow_view` + +```text +******************************************************************************** +* Name: map_workflow_view.py +* Author: nswain +* Created On: November 21, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `MapWorkflowView(MapView, ResourceWorkflowView)` \{#mapworkflowview\} + +```text +Controller for a map view with workflows integration. +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, *args, **kwargs)` \{#mapworkflowview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): the session. + resource(Resource): the resource for this request. + context(dict): The context dictionary. + workflow_id(str): The id of the workflow. + step_id(str): The id of the step. + +Returns: + dict: modified context dictionary. +``` + +*`@staticmethod`* + +##### `set_feature_selection(map_view, enabled=True)` \{#mapworkflowview-set-feature-selection\} + +```text +Set whether features are selectable or not. +Args: + map_view(MapView): The MapView gizmo options object. + enabled(bool): True to enable selection, False to disable it. +``` + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#mapworkflowview-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + +*`@staticmethod`* + +##### `get_geometry_data_for_previous_steps(current_step)` \{#mapworkflowview-get-geometry-data-for-previous-steps\} + +```text +Retrieve geometry data from previous workflow steps + +Args: + current_step (ResourceWorkflowStep): the current workflow step for which geometry data is being retrieved. + +Returns: + list[(ResourceWorkflowStep, str)]: a list of tuples, where each tuple contains a previous step and + its GeoJSON string +``` + + +##### `add_layers_for_previous_steps(self, request, resource, current_step, map_view, layer_groups, selectable=None)` \{#mapworkflowview-add-layers-for-previous-steps\} + +```text +Create layers for previous steps that have a spatial component to them for review of the previous steps. +Args: + request(HttpRequest): The request. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + map_view(MapView): The Tethys MapView object. + layer_groups(list<dict>): List of layer group dictionaries for new layers to add. + selectable(bool): Layers generated for previous steps are selectable when True. + +Returns: + MapView, list<dict>: The updated MapView and layer groups. +``` diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/spatial_condor_job_mwv.mdx b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_condor_job_mwv.mdx new file mode 100644 index 00000000..a8d87e50 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_condor_job_mwv.mdx @@ -0,0 +1,169 @@ +--- +id: controllers.resource_workflows.map_workflows.spatial_condor_job_mwv +title: tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_condor_job_mwv +sidebar_label: spatial_condor_job_mwv +--- + +# `tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_condor_job_mwv` + +```text +******************************************************************************** +* Name: spatial_input_mwv.py +* Author: nswain +* Created On: January 21, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialCondorJobMWV(MapWorkflowView)` \{#spatialcondorjobmwv\} + +```text +Controller for a map workflow view requiring spatial input (drawing). +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#spatialcondorjobmwv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `on_get_step(self, request, session, resource, workflow, current_step, previous_step, next_step, *args, **kwargs)` \{#spatialcondorjobmwv-on-get-step\} + +```text +Hook that is called at the beginning of the get request for a workflow step, before any other controller logic occurs. + request(HttpRequest): The request. + session(sqlalchemy.Session): the session. + resource(Resource): the resource for this request. + workflow(ResourceWorkflow): The current workflow. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +Returns: + None or HttpResponse: If an HttpResponse is returned, render that instead. +``` + + +##### `render_condor_jobs_table(self, request, session, resource, workflow, current_step, previous_step, next_step)` \{#spatialcondorjobmwv-render-condor-jobs-table\} + +```text +Render a condor jobs table showing the status of the current job that is processing. + request(HttpRequest): The request. + session(sqlalchemy.Session): the session. + resource(Resource): the resource for this request. + workflow(ResourceWorkflow): The current workflow. + current_step(ResourceWorkflowStep): The current step to be rendered. +Returns: + HttpResponse: The condor job table view. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#spatialcondorjobmwv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): The resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` + + +##### `run_job(self, request, session, resource, workflow_id, step_id, *args, **kwargs)` \{#spatialcondorjobmwv-run-job\} + +```text +Handle run-job-form requests: prepare and submit the condor job. +``` + + +##### `handle_on_submit_locking(self, request, session, resource, step)` \{#spatialcondorjobmwv-handle-on-submit-locking\} + +```text +Acquires or releases the workflow or resource lock based on the step options. + +Args: + request(HttpRequest): Django request instance. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + step(ResourceWorkflowStep): the step. +``` + +*`@staticmethod`* + +##### `get_working_directory(request, app)` \{#spatialcondorjobmwv-get-working-directory\} + +```text +Derive the working directory for the workflow. + +Args: + request(HttpRequest): Django request instance. + app(TethysAppBase): App class or instance. + +Returns: + str: Path to working directory for the workflow. +``` + +*`@staticmethod`* + +##### `serialize_parameters(step)` \{#spatialcondorjobmwv-serialize-parameters\} + +```text +Serialize parameters from previous steps into a file for sending with the workflow. + +Args: + step(ResourceWorkflowStep): The current step. + +Returns: + str: path to the file containing serialized parameters. +``` + + +##### `process_lock_options_on_init(self, request, session, resource, step)` \{#spatialcondorjobmwv-process-lock-options-on-init\} + +```text +Process lock options when the view initializes. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + step(ResourceWorkflowStep): the step. +``` + + +##### `process_lock_options_after_submission(self, request, session, resource, step)` \{#spatialcondorjobmwv-process-lock-options-after-submission\} + +```text +Process lock options after the step has been submitted and processed. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + step(ResourceWorkflowStep): the step. +``` diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/spatial_data_mwv.mdx b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_data_mwv.mdx new file mode 100644 index 00000000..14aa23da --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_data_mwv.mdx @@ -0,0 +1,77 @@ +--- +id: controllers.resource_workflows.map_workflows.spatial_data_mwv +title: tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_data_mwv +sidebar_label: spatial_data_mwv +--- + +# `tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_data_mwv` + +```text +******************************************************************************** +* Name: spatial_data_mwv.py +* Author: nswain +* Created On: March 5, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialDataMWV(MapWorkflowView)` \{#spatialdatamwv\} + +```text +Abstract controller for a map workflow view data assigned to each feature. +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#spatialdatamwv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + +*`@workflow_step_controller(is_rest_controller=True)`* + +##### `get_popup_form(self, request, session, resource, workflow, step, back_url, *args, **kwargs)` \{#spatialdatamwv-get-popup-form\} + +```text +Handle GET requests with method get-attributes-form. +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + workflow(ResourceWorkflow): the workflow. + step(ResourceWorkflowStep): the step. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` + +*`@workflow_step_controller(is_rest_controller=True)`* + +##### `save_spatial_data(self, request, session, resource, workflow, step, back_url, *args, **kwargs)` \{#spatialdatamwv-save-spatial-data\} + +```text +Handle GET requests with method get-attributes-form. +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + workflow(ResourceWorkflow): the workflow. + step(ResourceWorkflowStep): the step. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/spatial_dataset_mwv.mdx b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_dataset_mwv.mdx new file mode 100644 index 00000000..97fbc65a --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_dataset_mwv.mdx @@ -0,0 +1,84 @@ +--- +id: controllers.resource_workflows.map_workflows.spatial_dataset_mwv +title: tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_dataset_mwv +sidebar_label: spatial_dataset_mwv +--- + +# `tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_dataset_mwv` + +```text +******************************************************************************** +* Name: spatial_dataset_mwv.py +* Author: nswain +* Created On: March 5, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialDatasetMWV(SpatialDataMWV)` \{#spatialdatasetmwv\} + +```text +Controller for a map workflow view requiring spatial input (drawing). +``` +#### Methods + +*`@workflow_step_controller(is_rest_controller=True)`* + +##### `get_popup_form(self, request, session, resource, workflow, step, back_url, *args, **kwargs)` \{#spatialdatasetmwv-get-popup-form\} + +```text +Handle GET requests with method get-attributes-form. +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + workflow(ResourceWorkflow): the workflow. + step(ResourceWorkflowStep): the step. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` + +*`@workflow_step_controller(is_rest_controller=True)`* + +##### `save_spatial_data(self, request, session, resource, workflow, step, back_url, *args, **kwargs)` \{#spatialdatasetmwv-save-spatial-data\} + +```text +Handle GET requests with method get-attributes-form. +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + workflow(ResourceWorkflow): the workflow. + step(ResourceWorkflowStep): the step. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#spatialdatasetmwv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): The resource being updated. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` diff --git a/website/docs/api/controllers/resource_workflows/map_workflows/spatial_input_mwv.mdx b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_input_mwv.mdx new file mode 100644 index 00000000..aade41ec --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/map_workflows/spatial_input_mwv.mdx @@ -0,0 +1,180 @@ +--- +id: controllers.resource_workflows.map_workflows.spatial_input_mwv +title: tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_input_mwv +sidebar_label: spatial_input_mwv +--- + +# `tethysext.atcore.controllers.resource_workflows.map_workflows.spatial_input_mwv` + +```text +******************************************************************************** +* Name: spatial_input_mwv.py +* Author: nswain +* Created On: January 21, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialInputMWV(MapWorkflowView)` \{#spatialinputmwv\} + +```text +Controller for a map workflow view requiring spatial input (drawing). +``` +#### Methods + + +##### `get_step_specific_context(self, request, session, context, current_step, previous_step, next_step)` \{#spatialinputmwv-get-step-specific-context\} + +```text +Hook for extending the view context. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. + +Returns: + dict: key-value pairs to add to context. +``` + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#spatialinputmwv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#spatialinputmwv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): the resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` + + +##### `validate_feature_attributes(self, request, session, resource, step_id, *args, **kwargs)` \{#spatialinputmwv-validate-feature-attributes\} + +```text +Handle feature attribute validation AJAX requests. +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + resource(Resource): the resource for this request. + step_id(str): ID of the step to render. + +Returns: + JsonResponse +``` + + +##### `parse_shapefile(self, request, in_memory_file)` \{#spatialinputmwv-parse-shapefile\} + +```text +Parse shapefile, serialize into GeoJSON, and validate. +Args: + request(HttpRequest): The request. + in_memory_file (InMemoryUploadedFile): A zip archive containing the shapefile that has been uploaded. + +Returns: + dict: Dictionary equivalent of GeoJSON. +``` + + +##### `validate_projection(self, proj_str)` \{#spatialinputmwv-validate-projection\} + +```text +Validate the projection of uploaded shapefiles. Currently only support the WGS 1984 Geographic Projection (EPSG:4326). + +Args: + proj_str(str): Well-Known-Text projection string. + +Raises: + ValueError: unsupported projection systems. +``` + +*`@staticmethod`* + +##### `parse_drawn_geometry(geometry)` \{#spatialinputmwv-parse-drawn-geometry\} + +```text +Parse the geometry into GeoJSON and validate. + +Args: + geometry (str): GeoJSON string containing at least one feature. + +Returns: + dict: Dictionary equivalent of GeoJSON. +``` + +*`@staticmethod`* + +##### `combine_geojson_objects(shapefile_geojson, geometry_geojson)` \{#spatialinputmwv-combine-geojson-objects\} + +```text +Merge two geojson objects. +Args: + shapefile_geojson: geojson object derived from shapefile. + geometry_geojson: geojson object derived from drawing. + +Returns: + object: geojson object. +``` + +*`@staticmethod`* + +##### `post_process_geojson(geojson)` \{#spatialinputmwv-post-process-geojson\} + +```text +Standardize GeoJSON format and add IDs. Note: OpenLayers is pretty finicky about the format of the geojson for mapping properties to the ol.Feature objects. + +Args: + geojson: geojson object derived from input (drawing and/or shapefile. + +Returns: + object: geojson object. +``` + + +##### `store_imagery(self, request, step, in_memory_file)` \{#spatialinputmwv-store-imagery\} + +```text +Store imagery file on the geoserver. Uses the step name and file name for the store id. + +Args: + request(HttpRequest): The request. + step(ResourceWorkflowStep): The workflow step. + in_memory_file(InMemoryUploadedFile): A GeoTiff image that has been uploaded. + +Returns: + str: The layer_id of the image stored. +``` diff --git a/website/docs/api/controllers/resource_workflows/mixins.mdx b/website/docs/api/controllers/resource_workflows/mixins.mdx new file mode 100644 index 00000000..765c79ea --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/mixins.mdx @@ -0,0 +1,91 @@ +--- +id: controllers.resource_workflows.mixins +title: tethysext.atcore.controllers.resource_workflows.mixins +sidebar_label: mixins +--- + +# `tethysext.atcore.controllers.resource_workflows.mixins` + +> _No description._ + +## Classes + + +### `WorkflowViewMixin(ResourceViewMixin)` \{#workflowviewmixin\} + +```text +Mixin for class-based views that adds convenience methods for working with resources and workflows. +``` +#### Methods + + +##### `get_resource_workflow_model(self)` \{#workflowviewmixin-get-resource-workflow-model\} + +> _No description._ + + + +##### `get_resource_workflow_step_model(self)` \{#workflowviewmixin-get-resource-workflow-step-model\} + +> _No description._ + + + +##### `get_workflow(self, request, workflow_id, session=None)` \{#workflowviewmixin-get-workflow\} + +```text +Get the workflow and check permissions. + +Args: + request: Django HttpRequest. + workflow_id: ID of the workflow. + session: SQLAlchemy session. Optional + +Returns: + ResourceWorkflow: the resource. +``` + + +##### `get_step(self, request, step_id, session=None)` \{#workflowviewmixin-get-step\} + +```text +Get the step and check permissions. + +Args: + request: Django HttpRequest. + step_id: ID of the step to get. + session: SQLAlchemy session. + +Returns: + ResourceWorkflow: the resource. +``` + + + +### `ResultViewMixin(ResourceViewMixin)` \{#resultviewmixin\} + +```text +Mixin for class-based views that adds convenience methods for working with resources, workflows, and results. +``` +#### Methods + + +##### `get_resource_workflow_result_model(self)` \{#resultviewmixin-get-resource-workflow-result-model\} + +> _No description._ + + + +##### `get_result(self, request, result_id, session=None)` \{#resultviewmixin-get-result\} + +```text +Get the workflow and check permissions. + +Args: + request: Django HttpRequest. + result_id: ID of the workflow. + session: SQLAlchemy session. Optional + +Returns: + ResourceWorkflow: the resource. +``` diff --git a/website/docs/api/controllers/resource_workflows/resource_workflow_router.mdx b/website/docs/api/controllers/resource_workflows/resource_workflow_router.mdx new file mode 100644 index 00000000..f5f2a055 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/resource_workflow_router.mdx @@ -0,0 +1,83 @@ +--- +id: controllers.resource_workflows.resource_workflow_router +title: tethysext.atcore.controllers.resource_workflows.resource_workflow_router +sidebar_label: resource_workflow_router +--- + +# `tethysext.atcore.controllers.resource_workflows.resource_workflow_router` + +```text +******************************************************************************** +* Name: resource_workflow_view.py +* Author: nswain +* Created On: November 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflowRouter(WorkflowViewMixin)` \{#resourceworkflowrouter\} + +```text +Router for resource workflow views. Routes to appropriate step controller. +``` +#### Methods + + +##### `get(self, request, resource_id, workflow_id, step_id=None, result_id=None, *args, **kwargs)` \{#resourceworkflowrouter-get\} + +```text +Route GET requests. + +Controller for the following url patterns: + +/resource/<resource_id>/my-custom-workflow/<workflow_id>/ +/resource/<resource_id>/my-custom-workflow/<workflow_id>/step/<step_id>/ +/resource/<resource_id>/my-custom-workflow/<workflow_id>/step/<step_id>/result/<result_id>/ + +Args: + request(HttpRequest): The request. + resource_id(str): ID of the resource this workflow applies to. + workflow_id(str): ID of the workflow. + step_id(str): ID of the step to render. Optional. Required if result_id given. + result_id(str): ID of the result to render. Optional. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` + + +##### `post(self, request, resource_id, workflow_id, step_id, result_id=None, *args, **kwargs)` \{#resourceworkflowrouter-post\} + +```text +Route POST requests. +Args: + request(HttpRequest): The request. + resource_id(str): ID of the resource this workflow applies to. + workflow_id(str): ID of the workflow. + step_id(str): ID of the step to render. + result_id(str): ID of the result to render. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` + + +##### `delete(self, request, resource_id, workflow_id, step_id, result_id=None, *args, **kwargs)` \{#resourceworkflowrouter-delete\} + +```text +Route DELETE requests. +Args: + request(HttpRequest): The request. + resource_id(str): ID of the resource this workflow applies to. + workflow_id(str): ID of the workflow. + step_id(str): ID of the step to render. + result_id(str): ID of the result to render. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` diff --git a/website/docs/api/controllers/resource_workflows/results_views/_category_.json b/website/docs/api/controllers/resource_workflows/results_views/_category_.json new file mode 100644 index 00000000..adcfd9af --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/results_views/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "results_views", + "position": 7 +} diff --git a/website/docs/api/controllers/resource_workflows/results_views/dataset_workflow_results_view.mdx b/website/docs/api/controllers/resource_workflows/results_views/dataset_workflow_results_view.mdx new file mode 100644 index 00000000..0b29a546 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/results_views/dataset_workflow_results_view.mdx @@ -0,0 +1,44 @@ +--- +id: controllers.resource_workflows.results_views.dataset_workflow_results_view +title: tethysext.atcore.controllers.resource_workflows.results_views.dataset_workflow_results_view +sidebar_label: dataset_workflow_results_view +--- + +# `tethysext.atcore.controllers.resource_workflows.results_views.dataset_workflow_results_view` + +```text +******************************************************************************** +* Name: dataset_workflow_result_view.py +* Author: nswain +* Created On: June 3, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `DatasetWorkflowResultView(WorkflowResultsView)` \{#datasetworkflowresultview\} + +```text +Dataset Result View Controller +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, result_id, *args, **kwargs)` \{#datasetworkflowresultview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + workflow_id (str): The id of the workflow. + step_id (str): The id of the step. + result_id (str): The id of the result. + +Returns: + dict: modified context dictionary. +``` diff --git a/website/docs/api/controllers/resource_workflows/results_views/index.mdx b/website/docs/api/controllers/resource_workflows/results_views/index.mdx new file mode 100644 index 00000000..3cde3112 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/results_views/index.mdx @@ -0,0 +1,17 @@ +--- +id: controllers.resource_workflows.results_views.index +title: tethysext.atcore.controllers.resource_workflows.results_views +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.resource_workflows.results_views` + +> _No description._ + +## Modules + +- [`dataset_workflow_results_view`](./dataset_workflow_results_view.mdx) +- [`map_workflow_results_view`](./map_workflow_results_view.mdx) +- [`plot_workflow_results_view`](./plot_workflow_results_view.mdx) +- [`report_workflow_results_view`](./report_workflow_results_view.mdx) diff --git a/website/docs/api/controllers/resource_workflows/results_views/map_workflow_results_view.mdx b/website/docs/api/controllers/resource_workflows/results_views/map_workflow_results_view.mdx new file mode 100644 index 00000000..b96a615a --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/results_views/map_workflow_results_view.mdx @@ -0,0 +1,77 @@ +--- +id: controllers.resource_workflows.results_views.map_workflow_results_view +title: tethysext.atcore.controllers.resource_workflows.results_views.map_workflow_results_view +sidebar_label: map_workflow_results_view +--- + +# `tethysext.atcore.controllers.resource_workflows.results_views.map_workflow_results_view` + +```text +******************************************************************************** +* Name: map_workflow_results_view.py +* Author: nswain +* Created On: October 15, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `MapWorkflowResultsView(MapWorkflowView, WorkflowResultsView)` \{#mapworkflowresultsview\} + +```text +Map Result View controller. +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, result_id, *args, **kwargs)` \{#mapworkflowresultsview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + +Returns: + dict: modified context dictionary. +``` + + +##### `get_plot_data(self, request, session, resource, result_id, *args, **kwargs)` \{#mapworkflowresultsview-get-plot-data\} + +```text +Load plot from given parameters. + +Args: + request (HttpRequest): The request. + session(sqlalchemy.Session): The database session. + resource(Resource): The resource. + +Returns: + JsonResponse: title, data, and layout options for the plot. +``` + + +##### `get_plot_for_geojson(self, layer, feature_id)` \{#mapworkflowresultsview-get-plot-for-geojson\} + +```text +Retrieves plot for feature from given layer. + +Args: + layer(dict): layer dictionary. + feature_id(str): id of the feature in the layer to plot. + +Returns: + title, data, layout: Plot dictionary. +``` + + +##### `update_result_layer(self, request, session, resource, *args, **kwargs)` \{#mapworkflowresultsview-update-result-layer\} + +```text +Update color ramp of a layer in the result. In the future, we can add more things to update here. +``` diff --git a/website/docs/api/controllers/resource_workflows/results_views/plot_workflow_results_view.mdx b/website/docs/api/controllers/resource_workflows/results_views/plot_workflow_results_view.mdx new file mode 100644 index 00000000..80257b9a --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/results_views/plot_workflow_results_view.mdx @@ -0,0 +1,44 @@ +--- +id: controllers.resource_workflows.results_views.plot_workflow_results_view +title: tethysext.atcore.controllers.resource_workflows.results_views.plot_workflow_results_view +sidebar_label: plot_workflow_results_view +--- + +# `tethysext.atcore.controllers.resource_workflows.results_views.plot_workflow_results_view` + +```text +******************************************************************************** +* Name: plot_workflow_result_view.py +* Author: nathan, htran, msouff +* Created On: Oct 7, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `PlotWorkflowResultView(WorkflowResultsView)` \{#plotworkflowresultview\} + +```text +Plot Result View Controller +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, result_id, *args, **kwargs)` \{#plotworkflowresultview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + workflow_id (str): The id of the workflow. + step_id (str): The id of the step. + result_id (str): The id of the result. + +Returns: + dict: modified context dictionary. +``` diff --git a/website/docs/api/controllers/resource_workflows/results_views/report_workflow_results_view.mdx b/website/docs/api/controllers/resource_workflows/results_views/report_workflow_results_view.mdx new file mode 100644 index 00000000..2466ae2e --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/results_views/report_workflow_results_view.mdx @@ -0,0 +1,69 @@ +--- +id: controllers.resource_workflows.results_views.report_workflow_results_view +title: tethysext.atcore.controllers.resource_workflows.results_views.report_workflow_results_view +sidebar_label: report_workflow_results_view +--- + +# `tethysext.atcore.controllers.resource_workflows.results_views.report_workflow_results_view` + +```text +******************************************************************************** +* Name: report_workflow_results_view.py +* Author: nswain, htran, msouffront +* Created On: October 14, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ReportWorkflowResultsView(MapWorkflowView, WorkflowResultsView)` \{#reportworkflowresultsview\} + +```text +Report Result View controller. +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#reportworkflowresultsview-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, result_id, *args, **kwargs)` \{#reportworkflowresultsview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + workflow_id (int): The workflow id. + step_id (int): The step id. + result_id (int): The result id. + +Returns: + dict: modified context dictionary. +``` + +*`@staticmethod`* + +##### `geoserver_url(link)` \{#reportworkflowresultsview-geoserver-url\} + +```text +link: 'http://admin:geoserver@192.168.99.163:8181/geoserver/wms/' +:return: 'http://192.168.99.163:8181/geoserver/wms/' +``` diff --git a/website/docs/api/controllers/resource_workflows/workflow_results_view.mdx b/website/docs/api/controllers/resource_workflows/workflow_results_view.mdx new file mode 100644 index 00000000..565b4991 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_results_view.mdx @@ -0,0 +1,118 @@ +--- +id: controllers.resource_workflows.workflow_results_view +title: tethysext.atcore.controllers.resource_workflows.workflow_results_view +sidebar_label: workflow_results_view +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_results_view` + +> _No description._ + +## Classes + + +### `WorkflowResultsView(ResourceWorkflowView, ResultViewMixin)` \{#workflowresultsview\} + +```text +Base class for result views. +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, result_id, *args, **kwargs)` \{#workflowresultsview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + workflow_id (str): UUID of the workflow. + step_id (str): UUID of the step. + result_id (str): UUID of the result. + +Returns: + dict: modified context dictionary. +``` + +*`@staticmethod`* + +##### `get_result_url_name(request, workflow)` \{#workflowresultsview-get-result-url-name\} + +```text +Derive url map name for the given result view. +Args: + request(HttpRequest): The request. + workflow(ResourceWorkflow): The current workflow. + +Returns: + str: name of the url pattern for the given workflow step views. +``` + + +##### `build_result_cards(self, step)` \{#workflowresultsview-build-result-cards\} + +```text +Build cards used by template to render the list of steps for the workflow. +Args: + step(ResourceWorkflowStep): the step to which the results belong. + +Returns: + list<dict>: one dictionary for each result in the step. +``` + + +##### `validate_result(self, request, session, result)` \{#workflowresultsview-validate-result\} + +```text +Validate the result being used for this view. Raises TypeError if result is invalid. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + result(ResourceWorkflowResult): The result to be rendered. + +Raises: + TypeError: if step is invalid. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#workflowresultsview-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): The resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#workflowresultsview-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` diff --git a/website/docs/api/controllers/resource_workflows/workflow_view.mdx b/website/docs/api/controllers/resource_workflows/workflow_view.mdx new file mode 100644 index 00000000..ec9afa7e --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_view.mdx @@ -0,0 +1,382 @@ +--- +id: controllers.resource_workflows.workflow_view +title: tethysext.atcore.controllers.resource_workflows.workflow_view +sidebar_label: workflow_view +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_view` + +```text +******************************************************************************** +* Name: workflow_view.py +* Author: nswain +* Created On: November 21, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflowView(ResourceView, WorkflowViewMixin)` \{#resourceworkflowview\} + +```text +Base class for workflow views. +``` +#### Methods + + +##### `get_context(self, request, session, resource, context, workflow_id, step_id, *args, **kwargs)` \{#resourceworkflowview-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. This method is called during initialization of the view. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + context (dict): The context dictionary. + workflow_id (str): The id of the workflow. + step_id (str): The id of the step. + +Returns: + dict: modified context dictionary. +``` + +*`@workflow_step_controller()`* + +##### `save_step_data(self, request, session, resource, workflow, step, back_url, *args, **kwargs)` \{#resourceworkflowview-save-step-data\} + +```text +Handle POST requests with input named "method" with value "save-step-data". This is called at end-of-life for the view. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + workflow(ResourceWorkflow): the workflow. + step(ResourceWorkflowStep): the step. + args, kwargs: Additional arguments passed to the controller. + +Returns: + HttpResponse: A Django response. +``` + + +##### `build_step_cards(self, request, workflow)` \{#resourceworkflowview-build-step-cards\} + +```text +Build cards used by template to render the list of steps for the workflow. + +Args: + request (HttpRequest): The request. + workflow(ResourceWorkflow): the workflow with the steps to render. + +Returns: + list<dict>: one dictionary for each step in the workflow. +``` + +*`@staticmethod`* + +##### `get_step_url_name(request, workflow)` \{#resourceworkflowview-get-step-url-name\} + +```text +Derive url map name for the given workflow step views. +Args: + request(HttpRequest): The request. + workflow(ResourceWorkflow): The current workflow. + +Returns: + str: name of the url pattern for the given workflow step views. +``` + +*`@staticmethod`* + +##### `get_workflow_url_name(request, workflow)` \{#resourceworkflowview-get-workflow-url-name\} + +```text +Derive url map name for the given workflow view. +Args: + request(HttpRequest): The request. + workflow(ResourceWorkflow): The current workflow. + +Returns: + str: name of the url pattern for the given workflow views. +``` + +*`@staticmethod`* + +##### `build_lock_display_options(request, workflow)` \{#resourceworkflowview-build-lock-display-options\} + +```text +Build an object with the workflow lock indicator display options. +Args: + request(HttpRequest): The request. + workflow(ResourceWorkflow): the workflow. + +Returns: + dict<style,message,show>: Dictionary containing the display options for the workflow lock indicator. +``` + +*`@staticmethod`* + +##### `get_style_for_status(status)` \{#resourceworkflowview-get-style-for-status\} + +```text +Return appropriate style for given status. + +Args: + status(str): One of StatusMixin statuses. + +Returns: + str: style for the given status. +``` + + +##### `workflow_locked_for_request_user(self, request, workflow)` \{#resourceworkflowview-workflow-locked-for-request-user\} + +```text +Checks if the workflow is locked for the request user--either directly or via the resource being locked. + +Args: + request(HttpRequest): The request. + workflow(ResourceWorkflow): the workflow. + +Returns: + bool: True if the workflow is locked. +``` + + +##### `user_has_active_role(self, request, step)` \{#resourceworkflowview-user-has-active-role\} + +```text +Checks if the request user has active role for step. + +Args: + request(HttpRequest): The request. + step(ResourceWorkflowStep): the step. + +Returns: + bool: True if user has active role. +``` + + +##### `is_read_only(self, request, step)` \{#resourceworkflowview-is-read-only\} + +```text +Determine if the view should be rendered in read-only mode. + +Args: + request(HttpRequest): The request. + step(ResourceWorkflowStep): The step. + +Returns: + bool: True if the view should be rendered in read-only mode. +``` + + +##### `process_lock_options_on_init(self, request, session, resource, step)` \{#resourceworkflowview-process-lock-options-on-init\} + +```text +Process lock options when the view initializes. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + step(ResourceWorkflowStep): the step. +``` + + +##### `process_lock_options_after_submission(self, request, session, resource, step)` \{#resourceworkflowview-process-lock-options-after-submission\} + +```text +Process lock options after the step has been submitted and processed. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): Session bound to the resource, workflow, and step instances. + resource(Resource): the resource this workflow applies to. + step(ResourceWorkflowStep): the step. +``` + +*`@staticmethod`* + +##### `acquire_lock_and_log(request, session, lockable, for_all_users=False)` \{#resourceworkflowview-acquire-lock-and-log\} + +```text +Attempt to acquire the lock on the lockable object and log the outcome. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + lockable(UserLockMixin): Object on which to acquire a lock. + for_all_users(bool): Lock for all users when True. +``` + +*`@staticmethod`* + +##### `release_lock_and_log(request, session, lockable)` \{#resourceworkflowview-release-lock-and-log\} + +```text +Attempt to release the lock on the lockable object and log the outcome. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + lockable(UserLockMixin): Object to on which to release a lock. +``` + + +##### `validate_step(self, request, session, current_step, previous_step, next_step)` \{#resourceworkflowview-validate-step\} + +```text +Validate the step being used for this view. Raises TypeError if current_step is invalid. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. + +Raises: + TypeError: if step is invalid. +``` + + +##### `on_get(self, request, session, resource, workflow_id, step_id, *args, **kwargs)` \{#resourceworkflowview-on-get\} + +```text +Override hook that is called at the beginning of the get request, before any other controller logic occurs. + +Args: + request (HttpRequest): The request. + session (sqlalchemy.Session): the session. + resource (Resource): the resource for this request. + +Returns: + None or HttpResponse: If an HttpResponse is returned, render that instead. +``` + + +##### `on_get_step(self, request, session, resource, workflow, current_step, previous_step, next_step, *args, **kwargs)` \{#resourceworkflowview-on-get-step\} + +```text +Hook that is called at the beginning of the get request for a workflow step, before any other controller logic occurs. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.Session): the session. + resource(Resource): the resource for this request. + workflow(ResourceWorkflow): The current workflow. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. + +Returns: + None or HttpResponse: If an HttpResponse is returned, render that instead. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#resourceworkflowview-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. Only called if the user has an active role. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): The resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` + + +##### `navigate_only(self, request, step, current_url, next_url, previous_url)` \{#resourceworkflowview-navigate-only\} + +```text +Navigate to next or previous step without processing/saving data. Called instead of process_step_data when the user doesn't have an active role. + +Args: + request(HttpRequest): The request. + step(ResourceWorkflowStep): The step to be updated. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. +``` + + +##### `next_or_previous_redirect(self, request, next_url, previous_url)` \{#resourceworkflowview-next-or-previous-redirect\} + +```text +Generate a redirect to either the next or previous step, depending on what button was pressed. + +Args: + request(HttpRequest): The request. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. +``` + +*`@abc.abstractmethod`* + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step, **kwargs)` \{#resourceworkflowview-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `extend_step_cards(self, workflow_step, step_status)` \{#resourceworkflowview-extend-step-cards\} + +```text +Hook for extending step card attributes. + +Args: + workflow_step(ResourceWorkflowStep): The current step for which a card is being created. + step_status(str): Status of the workflow_step. + +Returns: + dict: dictionary containing key-value attributes to add to the step card. +``` + + +##### `get_step_specific_context(self, request, session, context, current_step, previous_step, next_step)` \{#resourceworkflowview-get-step-specific-context\} + +```text +Hook for extending the view context. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. + +Returns: + dict: key-value pairs to add to context. +``` diff --git a/website/docs/api/controllers/resource_workflows/workflow_views/_category_.json b/website/docs/api/controllers/resource_workflows/workflow_views/_category_.json new file mode 100644 index 00000000..54925116 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_views/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "workflow_views", + "position": 8 +} diff --git a/website/docs/api/controllers/resource_workflows/workflow_views/form_input_wv.mdx b/website/docs/api/controllers/resource_workflows/workflow_views/form_input_wv.mdx new file mode 100644 index 00000000..4ca4450d --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_views/form_input_wv.mdx @@ -0,0 +1,60 @@ +--- +id: controllers.resource_workflows.workflow_views.form_input_wv +title: tethysext.atcore.controllers.resource_workflows.workflow_views.form_input_wv +sidebar_label: form_input_wv +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_views.form_input_wv` + +```text +******************************************************************************** +* Name: form_input_wv.py +* Author: mmlebaron, glarsen +* Created On: October 18, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `FormInputWV(ResourceWorkflowView)` \{#forminputwv\} + +```text +Controller for FormInputRWV. +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#forminputwv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#forminputwv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. Only called if the user has an active role. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): the resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. +``` diff --git a/website/docs/api/controllers/resource_workflows/workflow_views/index.mdx b/website/docs/api/controllers/resource_workflows/workflow_views/index.mdx new file mode 100644 index 00000000..98851bbd --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_views/index.mdx @@ -0,0 +1,23 @@ +--- +id: controllers.resource_workflows.workflow_views.index +title: tethysext.atcore.controllers.resource_workflows.workflow_views +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_views` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: August 19, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Modules + +- [`form_input_wv`](./form_input_wv.mdx) +- [`set_status_wv`](./set_status_wv.mdx) +- [`table_input_wv`](./table_input_wv.mdx) +- [`xms_tool_wv`](./xms_tool_wv.mdx) diff --git a/website/docs/api/controllers/resource_workflows/workflow_views/set_status_wv.mdx b/website/docs/api/controllers/resource_workflows/workflow_views/set_status_wv.mdx new file mode 100644 index 00000000..c4f72984 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_views/set_status_wv.mdx @@ -0,0 +1,64 @@ +--- +id: controllers.resource_workflows.workflow_views.set_status_wv +title: tethysext.atcore.controllers.resource_workflows.workflow_views.set_status_wv +sidebar_label: set_status_wv +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_views.set_status_wv` + +```text +******************************************************************************** +* Name: set_status_wv.py +* Author: nswain +* Created On: August 19, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SetStatusWV(ResourceWorkflowView)` \{#setstatuswv\} + +```text +Controller for SetStatusRWS. +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#setstatuswv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#setstatuswv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. Only called if the user has an active role. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): the resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` diff --git a/website/docs/api/controllers/resource_workflows/workflow_views/table_input_wv.mdx b/website/docs/api/controllers/resource_workflows/workflow_views/table_input_wv.mdx new file mode 100644 index 00000000..5569b2b6 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_views/table_input_wv.mdx @@ -0,0 +1,64 @@ +--- +id: controllers.resource_workflows.workflow_views.table_input_wv +title: tethysext.atcore.controllers.resource_workflows.workflow_views.table_input_wv +sidebar_label: table_input_wv +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_views.table_input_wv` + +```text +******************************************************************************** +* Name: table_input_wv.py +* Author: EJones +* Created On: April 17, 2024 +* Copyright: (c) Aquaveo 2024 +******************************************************************************** +``` +## Classes + + +### `TableInputWV(ResourceWorkflowView)` \{#tableinputwv\} + +```text +Controller for a workflow view for entering a 2D dataset in an spreadsheet-like table. +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#tableinputwv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#tableinputwv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): The resource being updated. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. + +Raises: + ValueError: exceptions that occur due to user error, provide helpful message to help user solve issue. + RuntimeError: exceptions that require developer attention. +``` diff --git a/website/docs/api/controllers/resource_workflows/workflow_views/xms_tool_wv.mdx b/website/docs/api/controllers/resource_workflows/workflow_views/xms_tool_wv.mdx new file mode 100644 index 00000000..e4787824 --- /dev/null +++ b/website/docs/api/controllers/resource_workflows/workflow_views/xms_tool_wv.mdx @@ -0,0 +1,79 @@ +--- +id: controllers.resource_workflows.workflow_views.xms_tool_wv +title: tethysext.atcore.controllers.resource_workflows.workflow_views.xms_tool_wv +sidebar_label: xms_tool_wv +--- + +# `tethysext.atcore.controllers.resource_workflows.workflow_views.xms_tool_wv` + +```text +******************************************************************************** +* Name: xms_tool_wv.py +* Author: dgallup +* Created On: December, 2023 +* Copyright: (c) Aquaveo 2023 +******************************************************************************** +``` +## Classes + + +### `XMSToolWV(ResourceWorkflowView)` \{#xmstoolwv\} + +```text +Controller for XMSToolRWS. +``` +#### Methods + + +##### `process_step_options(self, request, session, context, resource, current_step, previous_step, next_step)` \{#xmstoolwv-process-step-options\} + +```text +Hook for processing step options (i.e.: modify map or context based on step options). + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + context(dict): Context object for the map view template. + resource(Resource): the resource for this request. + current_step(ResourceWorkflowStep): The current step to be rendered. + previous_step(ResourceWorkflowStep): The previous step. + next_step(ResourceWorkflowStep): The next step. +``` + + +##### `process_step_data(self, request, session, step, resource, current_url, previous_url, next_url)` \{#xmstoolwv-process-step-data\} + +```text +Hook for processing user input data coming from the map view. Process form data found in request.POST and request.GET parameters and then return a redirect response to one of the given URLs. Only called if the user has an active role. + +Args: + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + step(ResourceWorkflowStep): The step to be updated. + resource(Resource): the resource for this request. + current_url(str): URL to step. + previous_url(str): URL to the previous step. + next_url(str): URL to the next step. + +Returns: + HttpResponse: A Django response. +``` + + +## Functions + + +### `generate_django_form_xmstool(xms_tool_class, form_values, resource=None, form_field_prefix=None, read_only=False, arg_mapping=None, setup_func=None)` \{#generate-django-form-xmstool\} + +```text +Create a Django form from a Parameterized object. + +Args: + xms_tool_class(class): the XMS tool class. + form_values(dict): dict of initial values to assign + form_field_prefix(str): A prefix to prepend to form fields + read_only(bool): Read only flag + arg_mapping(dict): Dictionary to map particular arguments to available resources +Returns: + Form: a Django form with fields matching the parameters of the given parameterized object. +``` diff --git a/website/docs/api/controllers/resources/_category_.json b/website/docs/api/controllers/resources/_category_.json new file mode 100644 index 00000000..4f445479 --- /dev/null +++ b/website/docs/api/controllers/resources/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "resources", + "position": 9 +} diff --git a/website/docs/api/controllers/resources/index.mdx b/website/docs/api/controllers/resources/index.mdx new file mode 100644 index 00000000..5f81864d --- /dev/null +++ b/website/docs/api/controllers/resources/index.mdx @@ -0,0 +1,21 @@ +--- +id: controllers.resources.index +title: tethysext.atcore.controllers.resources +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.resources` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: November 12, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Modules + +- [`tabbed_resource_details`](./tabbed_resource_details.mdx) +- [`tabs`](./tabs/index.mdx) diff --git a/website/docs/api/controllers/resources/tabbed_resource_details.mdx b/website/docs/api/controllers/resources/tabbed_resource_details.mdx new file mode 100644 index 00000000..53a41c34 --- /dev/null +++ b/website/docs/api/controllers/resources/tabbed_resource_details.mdx @@ -0,0 +1,118 @@ +--- +id: controllers.resources.tabbed_resource_details +title: tethysext.atcore.controllers.resources.tabbed_resource_details +sidebar_label: tabbed_resource_details +--- + +# `tethysext.atcore.controllers.resources.tabbed_resource_details` + +```text +******************************************************************************** +* Name: tabbed_resource_details.py +* Author: nswain +* Created On: November 12, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `TabbedResourceDetails(ResourceDetails)` \{#tabbedresourcedetails\} + +```text +Class-based view/controller that builds a tabbed details view for a Resource. + +Required URL Parameters: + resource_id (str): the ID of the Resource. + tab_slug (str): Portion of URL that denotes which tab is active. + +Properties: + template_name (str): The template that is used to render this view. + base_template (str): The base template from which the default template extends. + back_url (str): The URL that will be used for the back button on the view. + http_method_names (list): List of allowed HTTP methods. Defaults to ['get', 'post', 'delete']. + css_requirements (list<str>): A list of CSS files to load with the view. + js_requirements (list<str>): A list of JavaScript files to load with the view. + tabs (iterable<dict<tab_slug, tab_title, tab_view>>): List of dictionaries defining the tabs for this view. +``` +#### Methods + +*`@active_user_required()` `@permission_required('view_resources')` `@resource_controller()`* + +##### `get(self, request, session, resource, back_url, *args, tab_slug='', **kwargs)` \{#tabbedresourcedetails-get\} + +```text +Handle GET requests. +``` + +*`@active_user_required()` `@resource_controller()`* + +##### `post(self, request, session, resource, back_url, *args, tab_slug='', **kwargs)` \{#tabbedresourcedetails-post\} + +```text +Route POST requests. +``` + +*`@active_user_required()` `@resource_controller()`* + +##### `delete(self, request, session, resource, back_url, *args, tab_slug='', **kwargs)` \{#tabbedresourcedetails-delete\} + +```text +Route DELETE requests. +``` + + +##### `get_tab_view(self, request, resource, tab_slug, *args, **kwargs)` \{#tabbedresourcedetails-get-tab-view\} + +```text +Retrieve tab view that matches given tab_slug. + +Args: + request (HttpRequest): The request. + resource (str): Resource instance. + tab_slug (str): The slug of the Tab. + +Returns: + ResourceTabView: The ResourceTabView class or None if not found. +``` + + +##### `build_static_requirements(self, tabs)` \{#tabbedresourcedetails-build-static-requirements\} + +```text +Build the static (css and js) requirement lists. + +Args: + tabs (iterable): List of ResourceTabs. + +Returns: + 2-tuple: List of combined CSS requirements, List of combined JS requirements. +``` + + +##### `get_context(self, request, context, *args, **kwargs)` \{#tabbedresourcedetails-get-context\} + +```text +Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior. + +Args: + request (HttpRequest): The request. + context (dict): The context dictionary. +Returns: + dict: modified context dictionary. +``` + + +##### `get_tabs(self, request, resource, tab_slug, *args, **kwargs)` \{#tabbedresourcedetails-get-tabs\} + +```text +Hook to allow for more complex Tab definitions. Returns self.tabs if defined. + +Args: + request (HttpRequest): The request. + resource (str): Resource instance. + tab_slug (str): The slug of the Tab. + +Returns: + iterable<dict<tab_slug, tab_title, tab_view>>: List of dictionaries defining the tabs for this view. +``` diff --git a/website/docs/api/controllers/resources/tabs/_category_.json b/website/docs/api/controllers/resources/tabs/_category_.json new file mode 100644 index 00000000..409ef623 --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "tabs", + "position": 10 +} diff --git a/website/docs/api/controllers/resources/tabs/files_tab.mdx b/website/docs/api/controllers/resources/tabs/files_tab.mdx new file mode 100644 index 00000000..babe926c --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/files_tab.mdx @@ -0,0 +1,59 @@ +--- +id: controllers.resources.tabs.files_tab +title: tethysext.atcore.controllers.resources.tabs.files_tab +sidebar_label: files_tab +--- + +# `tethysext.atcore.controllers.resources.tabs.files_tab` + +```text +******************************************************************************** +* Name: files_tab.py +* Author: gagelarsen +* Created On: December 03, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceFilesTab(ResourceTab)` \{#resourcefilestab\} + +```text +A tab for the TabbedResourceDetails view that lists collections and files that are contained in those collections. + +Required URL Variables: + resource_id (str): the ID of the Resource. + tab_slug (str): Portion of URL that denotes which tab is active. + +Properties: + file_hide_patterns: A list of regular expression patterns for files that should not be shown in the files tab.fla + +Methods: + get_file_collections (required): Override this method to define a list of FileCollections that are shown in this tab. +``` +#### Methods + + +##### `get_file_collections(self, request, resource, session, *args, **kwargs)` \{#resourcefilestab-get-file-collections\} + +```text +Get the file_collections + +Returns: + A list of FileCollection clients. +``` + + +##### `get_context(self, request, session, resource, context, *args, **kwargs)` \{#resourcefilestab-get-context\} + +```text +Build context for the ResourceFilesTab template that is used to generate the tab content. +``` + + +##### `download_file(self, request, resource, session, *args, **kwargs)` \{#resourcefilestab-download-file\} + +```text +A function to download a file from a request. +``` diff --git a/website/docs/api/controllers/resources/tabs/index.mdx b/website/docs/api/controllers/resources/tabs/index.mdx new file mode 100644 index 00000000..a8320bb1 --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/index.mdx @@ -0,0 +1,24 @@ +--- +id: controllers.resources.tabs.index +title: tethysext.atcore.controllers.resources.tabs +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.resources.tabs` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: November 12, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Modules + +- [`files_tab`](./files_tab.mdx) +- [`resource_list_tab`](./resource_list_tab.mdx) +- [`resource_tab`](./resource_tab.mdx) +- [`summary_tab`](./summary_tab.mdx) +- [`workflows_tab`](./workflows_tab.mdx) diff --git a/website/docs/api/controllers/resources/tabs/resource_list_tab.mdx b/website/docs/api/controllers/resources/tabs/resource_list_tab.mdx new file mode 100644 index 00000000..a348d47d --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/resource_list_tab.mdx @@ -0,0 +1,62 @@ +--- +id: controllers.resources.tabs.resource_list_tab +title: tethysext.atcore.controllers.resources.tabs.resource_list_tab +sidebar_label: resource_list_tab +--- + +# `tethysext.atcore.controllers.resources.tabs.resource_list_tab` + +```text +******************************************************************************** +* Name: resource_list_tab.py +* Author: gagelarsen +* Created On: December 11, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceListTab(ResourceTab)` \{#resourcelisttab\} + +```text +A tab for the TabbedResourceDetails view that lists resources related to this resource. + +Required URL Variables: + resource_id (str): the ID of the Resource. + tab_slug (str): Portion of URL that denotes which tab is active. + +Methods: + get_resources (required): Get a list of resources to be associated with this resource. +``` +#### Methods + + +##### `get_resources(self, request, resource, session, *args, **kwargs)` \{#resourcelisttab-get-resources\} + +```text +Get a list of resources + +Returns: + A list of Resources. +``` + + +##### `get_href_for_resource(self, app_namespace, resource)` \{#resourcelisttab-get-href-for-resource\} + +```text +Hook to allow implementations of ResourceListTab to provide action href. +Args: + app_namespace (str): the namespace of the app. + resource (Resource): the current Resource. + +Returns: + str: the href for the given resource. +``` + + +##### `get_context(self, request, session, resource, context, *args, **kwargs)` \{#resourcelisttab-get-context\} + +```text +Build context for the ResourceFilesTab template that is used to generate the tab content. +``` diff --git a/website/docs/api/controllers/resources/tabs/resource_tab.mdx b/website/docs/api/controllers/resources/tabs/resource_tab.mdx new file mode 100644 index 00000000..77fa4343 --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/resource_tab.mdx @@ -0,0 +1,54 @@ +--- +id: controllers.resources.tabs.resource_tab +title: tethysext.atcore.controllers.resources.tabs.resource_tab +sidebar_label: resource_tab +--- + +# `tethysext.atcore.controllers.resources.tabs.resource_tab` + +```text +******************************************************************************** +* Name: resource_tab.py +* Author: nswain +* Created On: November 12, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceTab(ResourceView)` \{#resourcetab\} + +```text +A class-based view/controller that handles the lazily loaded content of a tab on the TabbedResourceDetails view. It should also handle all AJAX calls and form submissions specific to that tab. + +Required URL Variables: + resource_id (str): the ID of the Resource. + tab_slug (str): Portion of URL that denotes which tab is active. + +Properties: + template_name (str): The template that is used to render this view. + base_template (str): The base template from which the default template extends. + back_url (str): The URL that will be used for the back button on the view. + http_method_names (list): List of allowed HTTP methods. Defaults to ['get']. + css_requirements (list<str>): A list of CSS files to load with the view. + js_requirements (list<str>): A list of JavaScript files to load with the view. + modal_templates (list<str>): A list of templates containing modals for the view. + post_load_callback (str): The name of a JavaScript function to call after the tab has loaded. +``` +#### Methods + +*`@classmethod`* + +##### `get_tabbed_view_context(cls, request, context)` \{#resourcetab-get-tabbed-view-context\} + +```text +Hook for ResourceTab specific context that needs to be added to the TabbedResourceDetails view. This is usually used for adding variables that need to be used to build modals, which are loaded when the tabbed view loads. + +Args: + request(HttpRequest): Django HttpRequest. + context(dict): context object. + +Returns: + dict: with additional items to add to the context of the TabbedResourceDetails view. +``` diff --git a/website/docs/api/controllers/resources/tabs/summary_tab.mdx b/website/docs/api/controllers/resources/tabs/summary_tab.mdx new file mode 100644 index 00000000..714ea3f8 --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/summary_tab.mdx @@ -0,0 +1,82 @@ +--- +id: controllers.resources.tabs.summary_tab +title: tethysext.atcore.controllers.resources.tabs.summary_tab +sidebar_label: summary_tab +--- + +# `tethysext.atcore.controllers.resources.tabs.summary_tab` + +```text +******************************************************************************** +* Name: summary_tab.py +* Author: nswain +* Created On: November 12, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceSummaryTab(ResourceTab)` \{#resourcesummarytab\} + +```text +A tab for the TabbedResourceDetails view that lists key-value pair attributes of the Resource. The attributes can be grouped into multiple sections with titles. + +Required URL Variables: + resource_id (str): the ID of the Resource. + tab_slug (str): Portion of URL that denotes which tab is active. + +Properties: + has_preview_image (bool): Whether to load a preview image or not. Defaults to False. + preview_image_title (str): Title to display above the preview image. Defaults to "Preview". + +Methods: + get_summary_tab_info (required): Override this method to define the attributes that are shown in this tab. + get_preview_image_url (optional): Override this method to define the URL for the preview image to use. +``` +#### Methods + + +##### `get_preview_image_url(self, request, resource, *args, **kwargs)` \{#resourcesummarytab-get-preview-image-url\} + +```text +Define preview image URL for the summary tab. + +Returns: + str: the image URL. +``` + + +##### `get_summary_tab_info(self, request, session, resource, *args, **kwargs)` \{#resourcesummarytab-get-summary-tab-info\} + +```text +Get the summary tab info + +Return Format +[ + [ + ('Section 1 Title', {'key1': value}), + ('Section 2 Title', {'key1': value, 'key2': value}), + ], + [ + ('Section 3 Title', {'key1': value}), + ], +] +``` + + +##### `get_context(self, request, session, resource, context, *args, **kwargs)` \{#resourcesummarytab-get-context\} + +```text +Build context for the ResourceSummaryTab template that is used to generate the tab content. +``` + + +##### `load_summary_tab_preview_image(self, request, resource, *args, **kwargs)` \{#resourcesummarytab-load-summary-tab-preview-image\} + +```text +Render the summary tab preview image. + +Returns: + HttpResponse: rendered template. +``` diff --git a/website/docs/api/controllers/resources/tabs/workflows_tab.mdx b/website/docs/api/controllers/resources/tabs/workflows_tab.mdx new file mode 100644 index 00000000..bd117fa7 --- /dev/null +++ b/website/docs/api/controllers/resources/tabs/workflows_tab.mdx @@ -0,0 +1,130 @@ +--- +id: controllers.resources.tabs.workflows_tab +title: tethysext.atcore.controllers.resources.tabs.workflows_tab +sidebar_label: workflows_tab +--- + +# `tethysext.atcore.controllers.resources.tabs.workflows_tab` + +```text +******************************************************************************** +* Name: workflows_tab.py +* Author: nswain +* Created On: November 13, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflowsTab(ResourceTab)` \{#resourceworkflowstab\} + +```text +A tab for the TabbedResourceDetails view that lists any ResourceWorkflows associated with the Resource. Users can add and delete ResourceWorkflows and launch them from this view as well. + +Required URL Variables: + resource_id (str): the ID of the Resource. + tab_slug (str): Portion of URL that denotes which tab is active. + +Properties: + show_all_workflows (bool): List all workflows, not just those created by the current user. Defaults to True. + show_all_workflows_roles list<Roles>: List of user Roles that are allowed to see all workflows when show_all_workflows is False. Defaults to: [Roles.APP_ADMIN, Roles.DEVELOPER, Roles.ORG_ADMIN, Roles.ORG_REVIEWER]. + +Class Methods: + get_workflow_types (required): Return a dictionary mapping of ResourceWorkflow.TYPE to ResourceWorkflow classes (e.g. {MyResourceWorkflow.TYPE: MyResourceWorkflow} ). The list of available workflows in the New Workflow dialog is derived from this object. + +Methods: + get_map_manager (optional): Return your app-specific MapManager. Required if your workflows use spatial steps. + get_spatial_manager (optional): Return your app-specific SpatialManager. Required if your workflows use spatial steps. + get_sds_setting_name (optional): Return the name of the SpatialDatasetService setting. Required if your workflows use spatial steps. +``` +#### Methods + +*`@classmethod` `@abstractmethod`* + +##### `get_workflow_types(cls, request=None, context=None)` \{#resourceworkflowstab-get-workflow-types\} + +```text +A hook that must be used to define a the ResourceWorkflows supported by this tab view. The list of available workflows in the New Workflow dialog is derived from this object. + +request (HttpRequest): The requestion, optional. +context (dict): The context dictionary, optional. + +Returns: + dict: mapping of ResourceWorkflow.TYPE to ResourceWorkflow classes (e.g. {MyResourceWorkflow.TYPE: MyResourceWorkflow} ). +``` + + +##### `get_map_manager(self)` \{#resourceworkflowstab-get-map-manager\} + +```text +A hook that can be used to define your app-specific MapManager. Required if your workflows use spatial steps. + +Returns: + MapManagerBase: an app-specific MapMangerBase class. +``` + + +##### `get_spatial_manager(self)` \{#resourceworkflowstab-get-spatial-manager\} + +```text +A hook that can be used to define your app-specific SpatialManager. Required if your workflows use spatial steps. + +Returns: + BaseSpatialManager: an app-specific BaseSpatialManager class. +``` + + +##### `get_sds_setting_name(self)` \{#resourceworkflowstab-get-sds-setting-name\} + +```text +Return the name of the SpatialDatasetService setting. Required if your workflows use spatial steps. + +Returns: + str: the name of the SpatialDatasetService setting for your app. +``` + +*`@classmethod`* + +##### `get_tabbed_view_context(cls, request, context)` \{#resourceworkflowstab-get-tabbed-view-context\} + +```text +Add context specific to the ResourceWorkflowsTab to the TabbedResourceDetails view. +``` + + +##### `get_context(self, request, session, resource, context, *args, **kwargs)` \{#resourceworkflowstab-get-context\} + +```text +Build context for the ResourceWorkflowsTab template that is used to generate the tab content. +``` + + +##### `post(self, request, resource_id, *args, **kwargs)` \{#resourceworkflowstab-post\} + +```text +Handle the New Workflow form submissions for this tab. +``` + + +##### `delete(self, request, resource_id, *args, **kwargs)` \{#resourceworkflowstab-delete\} + +```text +Handle DELETE requests for this tab. +``` + + +##### `get_workflows_query(self, request, session, resource, app_user)` \{#resourceworkflowstab-get-workflows-query\} + +```text +Build the base SQLAlchemy query for workflows that are to be displayed in this tab. + +Args: + request (django.http.HttpRequest): Django request object. + session (sqlalchemy.orm.Session): SQLAlchemy session object. + resource (Resource): The resource. + app_user (AppUser): The App User. + +Returns: + sqlalchemy.orm.Query: An uncalled SQLAlchemy Query object. +``` diff --git a/website/docs/api/controllers/rest/_category_.json b/website/docs/api/controllers/rest/_category_.json new file mode 100644 index 00000000..bbb3f2fc --- /dev/null +++ b/website/docs/api/controllers/rest/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "rest", + "position": 11 +} diff --git a/website/docs/api/controllers/rest/index.mdx b/website/docs/api/controllers/rest/index.mdx new file mode 100644 index 00000000..bb44f08e --- /dev/null +++ b/website/docs/api/controllers/rest/index.mdx @@ -0,0 +1,20 @@ +--- +id: controllers.rest.index +title: tethysext.atcore.controllers.rest +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.controllers.rest` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: May 14, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Modules + +- [`spatial_reference`](./spatial_reference.mdx) diff --git a/website/docs/api/controllers/rest/spatial_reference.mdx b/website/docs/api/controllers/rest/spatial_reference.mdx new file mode 100644 index 00000000..74624128 --- /dev/null +++ b/website/docs/api/controllers/rest/spatial_reference.mdx @@ -0,0 +1,67 @@ +--- +id: controllers.rest.spatial_reference +title: tethysext.atcore.controllers.rest.spatial_reference +sidebar_label: spatial_reference +--- + +# `tethysext.atcore.controllers.rest.spatial_reference` + +```text +******************************************************************************** +* Name: spatial_reference.py +* Author: nswain +* Created On: May 14, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `QuerySpatialReference(TethysController)` \{#queryspatialreference\} + +```text +Controller for modify_resource page. + +POST: Handle spatial reference queries. +``` +#### Methods + + +##### `get(self, request, *args, **kwargs)` \{#queryspatialreference-get\} + +```text +Route post requests. +``` + + +##### `query_srid_by_id(self, request)` \{#queryspatialreference-query-srid-by-id\} + +```text +" +This controller is normally called by the select2 Ajax for looking up SRIDs from the SQL database +``` + + +##### `query_wkt_by_id(self, request)` \{#queryspatialreference-query-wkt-by-id\} + +```text +" +This controller is normally called by the select2 Ajax for looking up SRIDs from the SQL database +``` + + +##### `query_srid_by_query(self, request)` \{#queryspatialreference-query-srid-by-query\} + +```text +" +This controller is normally called by the select2 Ajax for looking up SRIDs from the SQL database +``` + + +##### `get_engine(self)` \{#queryspatialreference-get-engine\} + +```text +Get connection to database. +Returns: + sqlalchemy.engine: connection to database with spatial_ref_sys table. +``` diff --git a/website/docs/api/controllers/utilities.mdx b/website/docs/api/controllers/utilities.mdx new file mode 100644 index 00000000..cce1a7eb --- /dev/null +++ b/website/docs/api/controllers/utilities.mdx @@ -0,0 +1,30 @@ +--- +id: controllers.utilities +title: tethysext.atcore.controllers.utilities +sidebar_label: utilities +--- + +# `tethysext.atcore.controllers.utilities` + +```text +******************************************************************************** +* Name: utilities.py +* Author: glarsen, nswain +* Created On: December 18, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Functions + + +### `get_style_for_status(status)` \{#get-style-for-status\} + +```text +Return appropriate style for given status. + +Args: + status(str): One of StatusMixin statuses. + +Returns: + str: style for the given status. +``` diff --git a/website/docs/api/exceptions/_category_.json b/website/docs/api/exceptions/_category_.json new file mode 100644 index 00000000..3d5265d3 --- /dev/null +++ b/website/docs/api/exceptions/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "exceptions", + "position": 12 +} diff --git a/website/docs/api/exceptions/index.mdx b/website/docs/api/exceptions/index.mdx new file mode 100644 index 00000000..5ad0e6ff --- /dev/null +++ b/website/docs/api/exceptions/index.mdx @@ -0,0 +1,83 @@ +--- +id: exceptions.index +title: tethysext.atcore.exceptions +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.exceptions` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: April 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ATCoreException(Exception)` \{#atcoreexception\} + +> _No description._ + + + +### `ModelDatabaseError(ATCoreException)` \{#modeldatabaseerror\} + +> _No description._ + + + +### `ModelDatabaseInitializationError(ModelDatabaseError)` \{#modeldatabaseinitializationerror\} + +> _No description._ + + + +### `ModelFileDatabaseInitializationError(ModelDatabaseError)` \{#modelfiledatabaseinitializationerror\} + +> _No description._ + + + +### `UnboundFileCollectionError(Exception)` \{#unboundfilecollectionerror\} + +> _No description._ + + + +### `UnboundFileDatabaseError(Exception)` \{#unboundfiledatabaseerror\} + +> _No description._ + + + +### `FileCollectionNotFoundError(Exception)` \{#filecollectionnotfounderror\} + +> _No description._ + + + +### `FileCollectionItemNotFoundError(Exception)` \{#filecollectionitemnotfounderror\} + +> _No description._ + + + +### `FileDatabaseNotFoundError(Exception)` \{#filedatabasenotfounderror\} + +> _No description._ + + + +### `FileCollectionItemAlreadyExistsError(Exception)` \{#filecollectionitemalreadyexistserror\} + +> _No description._ + + + +### `InvalidSpatialResourceExtentTypeError(Exception)` \{#invalidspatialresourceextenttypeerror\} + +> _No description._ diff --git a/website/docs/api/forms/_category_.json b/website/docs/api/forms/_category_.json new file mode 100644 index 00000000..fa4ee0f4 --- /dev/null +++ b/website/docs/api/forms/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "forms", + "position": 13 +} diff --git a/website/docs/api/forms/index.mdx b/website/docs/api/forms/index.mdx new file mode 100644 index 00000000..29efe71d --- /dev/null +++ b/website/docs/api/forms/index.mdx @@ -0,0 +1,14 @@ +--- +id: forms.index +title: tethysext.atcore.forms +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.forms` + +> _No description._ + +## Modules + +- [`widgets`](./widgets/index.mdx) diff --git a/website/docs/api/forms/widgets/_category_.json b/website/docs/api/forms/widgets/_category_.json new file mode 100644 index 00000000..3a5b609c --- /dev/null +++ b/website/docs/api/forms/widgets/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "widgets", + "position": 14 +} diff --git a/website/docs/api/forms/widgets/index.mdx b/website/docs/api/forms/widgets/index.mdx new file mode 100644 index 00000000..c4c12f58 --- /dev/null +++ b/website/docs/api/forms/widgets/index.mdx @@ -0,0 +1,14 @@ +--- +id: forms.widgets.index +title: tethysext.atcore.forms.widgets +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.forms.widgets` + +> _No description._ + +## Modules + +- [`param_widgets`](./param_widgets.mdx) diff --git a/website/docs/api/forms/widgets/param_widgets.mdx b/website/docs/api/forms/widgets/param_widgets.mdx new file mode 100644 index 00000000..21907e9e --- /dev/null +++ b/website/docs/api/forms/widgets/param_widgets.mdx @@ -0,0 +1,30 @@ +--- +id: forms.widgets.param_widgets +title: tethysext.atcore.forms.widgets.param_widgets +sidebar_label: param_widgets +--- + +# `tethysext.atcore.forms.widgets.param_widgets` + +```text +******************************************************************************** +* Name: param_widgets.py +* Author: Scott Christensen and Nathan Swain +* Created On: January 18, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Functions + + +### `generate_django_form(parameterized_obj, form_field_prefix=None, read_only=False)` \{#generate-django-form\} + +```text +Create a Django form from a Parameterized object. + +Args: + parameterized_obj(Parameterized): the parameterized object. + form_field_prefix(str): A prefix to prepend to form fields +Returns: + Form: a Django form with fields matching the parameters of the given parameterized object. +``` diff --git a/website/docs/api/gizmos/_category_.json b/website/docs/api/gizmos/_category_.json new file mode 100644 index 00000000..b5a2873a --- /dev/null +++ b/website/docs/api/gizmos/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "gizmos", + "position": 15 +} diff --git a/website/docs/api/gizmos/index.mdx b/website/docs/api/gizmos/index.mdx new file mode 100644 index 00000000..b55d001b --- /dev/null +++ b/website/docs/api/gizmos/index.mdx @@ -0,0 +1,15 @@ +--- +id: gizmos.index +title: tethysext.atcore.gizmos +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.gizmos` + +> _No description._ + +## Modules + +- [`slide_sheet`](./slide_sheet.mdx) +- [`spatial_reference_select`](./spatial_reference_select.mdx) diff --git a/website/docs/api/gizmos/slide_sheet.mdx b/website/docs/api/gizmos/slide_sheet.mdx new file mode 100644 index 00000000..32e2377c --- /dev/null +++ b/website/docs/api/gizmos/slide_sheet.mdx @@ -0,0 +1,53 @@ +--- +id: gizmos.slide_sheet +title: tethysext.atcore.gizmos.slide_sheet +sidebar_label: slide_sheet +--- + +# `tethysext.atcore.gizmos.slide_sheet` + +```text +******************************************************************************** +* Name: slight_sheet +* Author: nswain +* Created On: May 15, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `SlideSheet(TethysGizmoOptions)` \{#slidesheet\} + +```text +Slide-out panel gizmo for presenting supplemental page content. +``` +#### Methods + + +##### `__init__(self, id='slide-sheet', content_template='', title='', attributes=None, classes='', **kwargs)` \{#slidesheet-init\} + +```text +constructor + +Args: + id(str): id of slide sheet. Use this to differentiate multiple slide sheets on the same page. + content_template(str): path to template to use for slide sheet content. + title(str): title for slide sheet. +``` + +*`@staticmethod`* + +##### `get_gizmo_js()` \{#slidesheet-get-gizmo-js\} + +```text +JavaScript specific to gizmo. +``` + +*`@staticmethod`* + +##### `get_gizmo_css()` \{#slidesheet-get-gizmo-css\} + +```text +CSS specific to gizmo . +``` diff --git a/website/docs/api/gizmos/spatial_reference_select.mdx b/website/docs/api/gizmos/spatial_reference_select.mdx new file mode 100644 index 00000000..e35164c0 --- /dev/null +++ b/website/docs/api/gizmos/spatial_reference_select.mdx @@ -0,0 +1,75 @@ +--- +id: gizmos.spatial_reference_select +title: tethysext.atcore.gizmos.spatial_reference_select +sidebar_label: spatial_reference_select +--- + +# `tethysext.atcore.gizmos.spatial_reference_select` + +```text +******************************************************************************** +* Name: spatial_reference_select.py +* Author: nswain +* Created On: May 14, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `SpatialReferenceSelect(TethysGizmoOptions)` \{#spatialreferenceselect\} + +```text +Spatial reference select input gizmo. +``` +#### Methods + + +##### `__init__(self, display_name='Spatial Reference System', name='spatial-ref-select', id='spatial-ref-select', placeholder='', query_delay=1000, min_length=2, initial=None, spatial_reference_service='', error='', **kwargs)` \{#spatialreferenceselect-init\} + +```text +constructor + +Args: + display_name(str): label for spatial reference select control. Defaults to Spatial "Reference System". + name(str): name of the spatial reference select control. Defaults to 'spatial-ref-select'. + id(str): id for spatial reference select control. No id assigned if not specified. + placeholder(str): placeholder to display when nothing is selected. + spatial_reference_service(str): spatial reference service url. + query_delay(int): miliseconds to wait before issueing query to server. + min_length(int): minimum length of query string required before issueing query to server. + initial(tuple or None): initial srid selected: (srid_display, srid_value). + error(str): error message to display on control. +``` + +*`@staticmethod`* + +##### `get_vendor_js()` \{#spatialreferenceselect-get-vendor-js\} + +```text +JavaScript vendor libraries +``` + +*`@staticmethod`* + +##### `get_vendor_css()` \{#spatialreferenceselect-get-vendor-css\} + +```text +CSS vendor libraries +``` + +*`@staticmethod`* + +##### `get_gizmo_js()` \{#spatialreferenceselect-get-gizmo-js\} + +```text +JavaScript specific to gizmo. +``` + +*`@staticmethod`* + +##### `get_gizmo_css()` \{#spatialreferenceselect-get-gizmo-css\} + +```text +CSS specific to gizmo . +``` diff --git a/website/docs/api/handlers.mdx b/website/docs/api/handlers.mdx new file mode 100644 index 00000000..be468c31 --- /dev/null +++ b/website/docs/api/handlers.mdx @@ -0,0 +1,23 @@ +--- +id: handlers +title: tethysext.atcore.handlers +sidebar_label: handlers +--- + +# `tethysext.atcore.handlers` + +```text +******************************************************************************** +* Name: handlers +* Author: msouffront +* Created On: Nov 12, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Functions + +*`@with_request`* + +### `panel_rws_handler(document)` \{#panel-rws-handler\} + +> _No description._ diff --git a/website/docs/api/index.mdx b/website/docs/api/index.mdx new file mode 100644 index 00000000..3c00c55a --- /dev/null +++ b/website/docs/api/index.mdx @@ -0,0 +1,26 @@ +--- +id: index +title: API Reference +sidebar_label: Overview +sidebar_position: 1 +slug: /api +--- + +# API Reference + +Reference documentation for the public modules in `tethysext.atcore`. Pages here are generated directly from the project source by `website/scripts/generate_api_docs.py`. Edit Python docstrings, not these files. + +## Subpackages and modules + +- [`cli`](./cli/index.mdx) +- [`controllers`](./controllers/index.mdx) +- [`exceptions`](./exceptions/index.mdx) +- [`forms`](./forms/index.mdx) +- [`gizmos`](./gizmos/index.mdx) +- [`handlers`](./handlers.mdx) +- [`mixins`](./mixins/index.mdx) +- [`models`](./models/index.mdx) +- [`permissions`](./permissions/index.mdx) +- [`services`](./services/index.mdx) +- [`urls`](./urls/index.mdx) +- [`utilities`](./utilities.mdx) diff --git a/website/docs/api/mixins/_category_.json b/website/docs/api/mixins/_category_.json new file mode 100644 index 00000000..ff644904 --- /dev/null +++ b/website/docs/api/mixins/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "mixins", + "position": 16 +} diff --git a/website/docs/api/mixins/attributes_mixin.mdx b/website/docs/api/mixins/attributes_mixin.mdx new file mode 100644 index 00000000..0e4d1e48 --- /dev/null +++ b/website/docs/api/mixins/attributes_mixin.mdx @@ -0,0 +1,87 @@ +--- +id: mixins.attributes_mixin +title: tethysext.atcore.mixins.attributes_mixin +sidebar_label: attributes_mixin +--- + +# `tethysext.atcore.mixins.attributes_mixin` + +```text +******************************************************************************** +* Name: attributes_mixin.py +* Author: nswain +* Created On: April 23, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `AttributesMixin(object)` \{#attributesmixin\} + +```text +Provides methods for implementing the attributes pattern. +``` +#### Methods + +*`@property`* + +##### `attributes(self)` \{#attributesmixin-attributes\} + +> _No description._ + + +*`@attributes.setter`* + +##### `attributes(self, value)` \{#attributesmixin-attributes\} + +> _No description._ + + + +##### `get_attribute(self, key, default=None)` \{#attributesmixin-get-attribute\} + +```text +Get value of a specific attribute. +Args: + key(str): key of attribute. + +Returns: + varies: value of attribute. +``` + + +##### `set_attribute(self, key, value)` \{#attributesmixin-set-attribute\} + +```text +Set value of a specific attribute. +Args: + key(str): key of attribute + value: value of attribute +``` + +*`@classmethod`* + +##### `build_attributes_string(cls, **kwargs)` \{#attributesmixin-build-attributes-string\} + +```text +Helper method that builds attributes string to use when querying. +Args: + **kwargs: any number of key value pairs to use for filtering. + +Returns: + json: kwargs serialized into a json string. +``` + +*`@classmethod`* + +##### `build_attributes(cls, **kwargs)` \{#attributesmixin-build-attributes\} + +```text +Helper method used to build attributes object. +Args: + **kwargs: any number of key value pairs to use for filtering. + +Returns: + dict: kwargs serialized into dictionary. +``` diff --git a/website/docs/api/mixins/file_collection_controller_mixin.mdx b/website/docs/api/mixins/file_collection_controller_mixin.mdx new file mode 100644 index 00000000..2423e637 --- /dev/null +++ b/website/docs/api/mixins/file_collection_controller_mixin.mdx @@ -0,0 +1,47 @@ +--- +id: mixins.file_collection_controller_mixin +title: tethysext.atcore.mixins.file_collection_controller_mixin +sidebar_label: file_collection_controller_mixin +--- + +# `tethysext.atcore.mixins.file_collection_controller_mixin` + +> _No description._ + +## Classes + + +### `FileCollectionsControllerMixin` \{#filecollectionscontrollermixin\} + +```text +Provides methods for controllers that manage on Resources with FileCollections. +``` +#### Methods + + +##### `get_app(self)` \{#filecollectionscontrollermixin-get-app\} + +```text +Usually implemented by other mixins or the controller. +``` + + +##### `delete_file_collections(self, session, resource, log)` \{#filecollectionscontrollermixin-delete-file-collections\} + +```text +Delete all FileCollections linked to the given resource. +``` + + +##### `get_file_collections_details(self, session, resource)` \{#filecollectionscontrollermixin-get-file-collections-details\} + +```text +Build summary details for each FileCollection associated with the given resource. + +Args: + session (Session): the SQLAlchemy session. + resource (Resource): a Resource with a file_collections relationship property. + +Returns: + list: a list of summary details table tuples, one for each FileCollection. +``` diff --git a/website/docs/api/mixins/file_collection_mixin.mdx b/website/docs/api/mixins/file_collection_mixin.mdx new file mode 100644 index 00000000..f2807af5 --- /dev/null +++ b/website/docs/api/mixins/file_collection_mixin.mdx @@ -0,0 +1,71 @@ +--- +id: mixins.file_collection_mixin +title: tethysext.atcore.mixins.file_collection_mixin +sidebar_label: file_collection_mixin +--- + +# `tethysext.atcore.mixins.file_collection_mixin` + +> _No description._ + +## Classes + + +### `FileCollectionMixin` \{#filecollectionmixin\} + +```text +Helpful methods for managing models with a file_collections relationship. Add this mixin to model classes, replacing the file_collection property with a relationship with FileCollections, ideally using backref so FileCollections doesn't need to be modified. +``` +#### Methods + +*`@classmethod`* + +##### `new(cls, file_database_client: FileDatabaseClient, files: List[str]=None, separate_collections: bool=False, **kwargs) -> Any` \{#filecollectionmixin-new\} + +```text +Create a new Resource in the given file_database with given files as contents. + +Args: + file_database_client (FileDatabaseClient): The FileDatabaseClient to use for new FileCollections. + files (List(str)): Files to be added to the Resource FileCollections. + separate_collections (bool): If true, each item given will be a new FileCollection + kwargs: Other attributes of the Resource to set when creating (e.g.: name='foo', description='bar') + +Returns: + A new Resource +``` + + +##### `export(self, file_database_client: FileDatabaseClient, target: str) -> None` \{#filecollectionmixin-export\} + +```text +Copy files contained in the collections to the target directory. + +Args: + file_database_client (FileDatabaseClient): The FileDatabaseClient bound to FileDatabase that contains the FileCollections. + target (str): The directory to export to. +``` + + +##### `duplicate(self, file_database_client: FileDatabaseClient) -> Any` \{#filecollectionmixin-duplicate\} + +```text +Create a new Resource that includes copies of all the FileCollections of this Resource. + +Args: + file_database_client (FileDatabaseClient): The FileDatabaseClient bound to FileDatabase that contains the FileCollections. + +Returns: + The new Resource. +``` + + +##### `delete_collections(self, root_directory: str, session: Session=None) -> None` \{#filecollectionmixin-delete-collections\} + +```text +Delete all associated file collections, including files on disk. + +Args: + root_directory (str or Path): directory that contains the FileDatabase. + session (sqlalchemy.Session): session for the SQL database. Optional. Required if Resource is not bound to a session. +``` diff --git a/website/docs/api/mixins/index.mdx b/website/docs/api/mixins/index.mdx new file mode 100644 index 00000000..2239a16e --- /dev/null +++ b/website/docs/api/mixins/index.mdx @@ -0,0 +1,22 @@ +--- +id: mixins.index +title: tethysext.atcore.mixins +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.mixins` + +> _No description._ + +## Modules + +- [`attributes_mixin`](./attributes_mixin.mdx) +- [`file_collection_controller_mixin`](./file_collection_controller_mixin.mdx) +- [`file_collection_mixin`](./file_collection_mixin.mdx) +- [`meta_mixin`](./meta_mixin.mdx) +- [`options_mixin`](./options_mixin.mdx) +- [`results_mixin`](./results_mixin.mdx) +- [`serialize_mixin`](./serialize_mixin.mdx) +- [`status_mixin`](./status_mixin.mdx) +- [`user_lock_mixin`](./user_lock_mixin.mdx) diff --git a/website/docs/api/mixins/meta_mixin.mdx b/website/docs/api/mixins/meta_mixin.mdx new file mode 100644 index 00000000..2f23c9aa --- /dev/null +++ b/website/docs/api/mixins/meta_mixin.mdx @@ -0,0 +1,52 @@ +--- +id: mixins.meta_mixin +title: tethysext.atcore.mixins.meta_mixin +sidebar_label: meta_mixin +--- + +# `tethysext.atcore.mixins.meta_mixin` + +```text +******************************************************************************** +* Name: file_database.py +* Author: glarsen +* Created On: November 2, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `MetaMixin(object)` \{#metamixin\} + +> _No description._ + +#### Methods + + +##### `write_meta(self)` \{#metamixin-write-meta\} + +```text +Write a __meta__.json file. Create it if it does not exist. +``` + + +##### `read_meta(self)` \{#metamixin-read-meta\} + +```text +Read a __meta__.json file. +``` + + +##### `get_meta(self, key)` \{#metamixin-get-meta\} + +```text +Property to get the meta from the underlying instance. +``` + + +##### `set_meta(self, key, value)` \{#metamixin-set-meta\} + +```text +Setter to set the meta on the underlying instance. +``` diff --git a/website/docs/api/mixins/options_mixin.mdx b/website/docs/api/mixins/options_mixin.mdx new file mode 100644 index 00000000..536ae94f --- /dev/null +++ b/website/docs/api/mixins/options_mixin.mdx @@ -0,0 +1,46 @@ +--- +id: mixins.options_mixin +title: tethysext.atcore.mixins.options_mixin +sidebar_label: options_mixin +--- + +# `tethysext.atcore.mixins.options_mixin` + +```text +******************************************************************************** +* Name: options_mixin.py +* Author: nswain +* Created On: April 30, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `OptionsMixin(object)` \{#optionsmixin\} + +```text +Provides methods for implementing the options pattern. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#optionsmixin-default-options\} + +```text +Returns default options dictionary for the object. +``` + +*`@property`* + +##### `options(self)` \{#optionsmixin-options\} + +> _No description._ + + +*`@options.setter`* + +##### `options(self, value)` \{#optionsmixin-options\} + +> _No description._ diff --git a/website/docs/api/mixins/results_mixin.mdx b/website/docs/api/mixins/results_mixin.mdx new file mode 100644 index 00000000..c51416ac --- /dev/null +++ b/website/docs/api/mixins/results_mixin.mdx @@ -0,0 +1,77 @@ +--- +id: mixins.results_mixin +title: tethysext.atcore.mixins.results_mixin +sidebar_label: results_mixin +--- + +# `tethysext.atcore.mixins.results_mixin` + +> _No description._ + +## Classes + + +### `ResultsMixin(object)` \{#resultsmixin\} + +```text +Provides convenience methods for managing a results. Requires attributes mixin. +``` +#### Methods + + +##### `get_result(self, result_id)` \{#resultsmixin-get-result\} + +```text +Get result with the given id. + +Args: + result_id(str or uuid): id of the result. + +Returns: + ResourceWorkflowResult: the result or None if not found. +``` + + +##### `get_result_by_codename(self, codename)` \{#resultsmixin-get-result-by-codename\} + +```text +Get a result by the given codename. +Args: + codename(str): codename of the result. + +Returns: + ResourceWorkflowResult: the result or None if not found. +``` + + +##### `get_last_result(self)` \{#resultsmixin-get-last-result\} + +```text +Get the result which was last viewed by the user. + +Returns: + ResourceWorkflowResult: the last result or None if not found. +``` + + +##### `set_last_result(self, result=None)` \{#resultsmixin-set-last-result\} + +```text +Set the id of the last result viewed by the user. + +Args: + result(ResourceWorkflowResult): The result to mark as being last viewed. +``` + + +##### `get_adjacent_results(self, result)` \{#resultsmixin-get-adjacent-results\} + +```text +Get the adjacent results the given result. + +Args: + result(ResourceWorkflowResult): A result belonging to this workflow. + +Returns: + ResourceWorkflowResult, ResourceWorkflowResult: previous and next results, respectively. +``` diff --git a/website/docs/api/mixins/serialize_mixin.mdx b/website/docs/api/mixins/serialize_mixin.mdx new file mode 100644 index 00000000..1caf8821 --- /dev/null +++ b/website/docs/api/mixins/serialize_mixin.mdx @@ -0,0 +1,77 @@ +--- +id: mixins.serialize_mixin +title: tethysext.atcore.mixins.serialize_mixin +sidebar_label: serialize_mixin +--- + +# `tethysext.atcore.mixins.serialize_mixin` + +> _No description._ + +## Classes + + +### `SerializeMixin` \{#serializemixin\} + +> _No description._ + +#### Methods + + +##### `serialize_base_fields(self, d: dict) -> dict` \{#serializemixin-serialize-base-fields\} + +```text +Hook for ATCore base classes to add their custom fields to serialization. + +Args: + d: Base serialized Resource dictionary. + +Returns: + Serialized Resource dictionary. +``` + + +##### `serialize_custom_fields(self, d: dict)` \{#serializemixin-serialize-custom-fields\} + +```text +Hook for app-specific subclasses to add additional fields to serialization. + +Args: + base: Base serialized Resource dictionary. + +Returns: + dict: Serialized Resource. +``` + + +##### `serialize_resource_props(self) -> dict` \{#serializemixin-serialize-resource-props\} + +```text +Serialize the normal Resource properties into a dictionary. + +Returns: + Serialized Resource dictionary. +``` + + +##### `json_dumps(self, d: dict) -> str` \{#serializemixin-json-dumps\} + +```text +Serialize this Resource to a json string. + +Returns: + str: JSON string. +``` + + +##### `serialize(self, format: str='dict')` \{#serializemixin-serialize\} + +```text +Serialize this Resource. + +Args: + format: Format to serialize to. One of 'dict' or 'json'. + +Returns: + dict: Serialized Resource. +``` diff --git a/website/docs/api/mixins/status_mixin.mdx b/website/docs/api/mixins/status_mixin.mdx new file mode 100644 index 00000000..56d2fd0e --- /dev/null +++ b/website/docs/api/mixins/status_mixin.mdx @@ -0,0 +1,46 @@ +--- +id: mixins.status_mixin +title: tethysext.atcore.mixins.status_mixin +sidebar_label: status_mixin +--- + +# `tethysext.atcore.mixins.status_mixin` + +> _No description._ + +## Classes + + +### `StatusMixin(object)` \{#statusmixin\} + +```text +Provides methods for implementing the status pattern. +``` +#### Methods + + +##### `__init__(self, *args, **kwargs)` \{#statusmixin-init\} + +> _No description._ + + +*`@classmethod`* + +##### `valid_statuses(cls)` \{#statusmixin-valid-statuses\} + +> _No description._ + + + +##### `get_status(self, key=ROOT_STATUS_KEY, default=None)` \{#statusmixin-get-status\} + +```text +Get status for a given value. +``` + + +##### `set_status(self, key=ROOT_STATUS_KEY, status=None)` \{#statusmixin-set-status\} + +```text +Set status for given key. +``` diff --git a/website/docs/api/mixins/user_lock_mixin.mdx b/website/docs/api/mixins/user_lock_mixin.mdx new file mode 100644 index 00000000..18befe9f --- /dev/null +++ b/website/docs/api/mixins/user_lock_mixin.mdx @@ -0,0 +1,97 @@ +--- +id: mixins.user_lock_mixin +title: tethysext.atcore.mixins.user_lock_mixin +sidebar_label: user_lock_mixin +--- + +# `tethysext.atcore.mixins.user_lock_mixin` + +```text +******************************************************************************** +* Name: user_lock_mixin.py +* Author: nswain +* Created On: September 24, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `UserLockMixin` \{#userlockmixin\} + +```text +Provides methods for implementing the user lock pattern. +``` +#### Methods + +*`@property`* + +##### `user_lock(self)` \{#userlockmixin-user-lock\} + +```text +Get the current value of the user lock. + +Returns: + str or None: the username of the user that has read-write access, LOCKED_FOR_ALL_USERS if locked for all users, or None if the user lock is not locked. +``` + +*`@property`* + +##### `is_user_locked(self)` \{#userlockmixin-is-user-locked\} + +```text +Check if the workflow is user locked. + +Returns: + bool: True if the workflow is user locked, False if not. +``` + +*`@property`* + +##### `is_locked_for_all_users(self)` \{#userlockmixin-is-locked-for-all-users\} + +```text +Check if the workflow is user locked for all users. + +Returns: + bool: True if the workflow is user locked for all users, False if not. +``` + + +##### `acquire_user_lock(self, request=None)` \{#userlockmixin-acquire-user-lock\} + +```text +Acquire a user lock for the given request user. Only the given user will be able to access the workflow in read-write mode. All other users will have read-only access. If no user is provided, the workflow will be locked for all users. + +Args: + request(django.http.HttpRequest): The Django Request. + +Returns: + bool: True if acquisition was successful. False if already locked. +``` + + +##### `release_user_lock(self, request)` \{#userlockmixin-release-user-lock\} + +```text +Release the user lock for the request user user. Only the user that was used to acquire the lock or other user with appropriate permissions (e.g. admin or staff user) can release a user lock. If the workflow is locked for all users, an admin will be required to unlock it. + +Args: + request(django.http.HttpRequest): The Django Request. + +Returns: + bool: True if release was successful or if the user lock was not locked. False otherwise. +``` + + +##### `is_locked_for_request_user(self, request)` \{#userlockmixin-is-locked-for-request-user\} + +```text +Determine if the lock is locked for the request user. + +Args: + request(django.http.HttpRequest): The Django Request. + +Returns: + bool: True if workflow is locked for request user. +``` diff --git a/website/docs/api/models/_category_.json b/website/docs/api/models/_category_.json new file mode 100644 index 00000000..c940570e --- /dev/null +++ b/website/docs/api/models/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "models", + "position": 17 +} diff --git a/website/docs/api/models/app_users/_category_.json b/website/docs/api/models/app_users/_category_.json new file mode 100644 index 00000000..0e0418c2 --- /dev/null +++ b/website/docs/api/models/app_users/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "app_users", + "position": 18 +} diff --git a/website/docs/api/models/app_users/app_user.mdx b/website/docs/api/models/app_users/app_user.mdx new file mode 100644 index 00000000..a77f1377 --- /dev/null +++ b/website/docs/api/models/app_users/app_user.mdx @@ -0,0 +1,324 @@ +--- +id: models.app_users.app_user +title: tethysext.atcore.models.app_users.app_user +sidebar_label: app_user +--- + +# `tethysext.atcore.models.app_users.app_user` + +> _No description._ + +## Classes + + +### `AppUser(AppUsersBase)` \{#appuser\} + +```text +Definition for the app_user table. All app users are associated with django users. +``` +#### Methods + + +##### `__init__(self, *args, **kwargs)` \{#appuser-init\} + +```text +Contstructor. +``` + +*`@reconstructor`* + +##### `init_on_load(self)` \{#appuser-init-on-load\} + +```text +Contstructor for the instances loaded from database +``` + +*`@validates('role')`* + +##### `validate_role(self, key, field)` \{#appuser-validate-role\} + +> _No description._ + + +*`@property`* + +##### `django_user(self)` \{#appuser-django-user\} + +> _No description._ + + +*`@property`* + +##### `email(self)` \{#appuser-email\} + +> _No description._ + + +*`@email.setter`* + +##### `email(self, value)` \{#appuser-email\} + +> _No description._ + + +*`@property`* + +##### `first_name(self)` \{#appuser-first-name\} + +> _No description._ + + +*`@first_name.setter`* + +##### `first_name(self, value)` \{#appuser-first-name\} + +> _No description._ + + +*`@property`* + +##### `last_name(self)` \{#appuser-last-name\} + +> _No description._ + + +*`@last_name.setter`* + +##### `last_name(self, value)` \{#appuser-last-name\} + +> _No description._ + + +*`@classmethod`* + +##### `get_app_user_from_request(cls, request, session, redirect_if_invalid=True)` \{#appuser-get-app-user-from-request\} + +```text +Get the AppUser cooresponding with the request user and redirect if no app user exists. +Args: + session(sqlalchemy.session): SQLAlchemy session object. + request(django.request): Django request object. + redirect_if_invalid (bool): Redirects to app library page if no app user is found for the request user. + +Returns: + AppUser: app user cooresponding with the request user or None if one does not exist and redirect is False. +``` + +*`@staticmethod`* + +##### `get_organization_model()` \{#appuser-get-organization-model\} + +> _No description._ + + +*`@staticmethod`* + +##### `get_resource_model()` \{#appuser-get-resource-model\} + +> _No description._ + + + +##### `is_staff(self)` \{#appuser-is-staff\} + +> _No description._ + + + +##### `get_display_name(self, default_to_username=True, append_username=False)` \{#appuser-get-display-name\} + +```text +Get a nice display name for an app user. +Args: + default_to_username: Return username if no other names are available if True. + append_username: Append the username in parenthesis if True. e.g.: "First Last (username)". + +Returns: In order of priority: "First Last", "First", "Last", "username". +``` + + +##### `get_django_user(self)` \{#appuser-get-django-user\} + +```text +Get the Django user object associated with this app user object + +Returns: Django User object +``` + + +##### `get_organizations(self, session, request, as_options=False, cascade=True, consultants=False)` \{#appuser-get-organizations\} + +```text +Get the Organizations to which the given user belongs. +Args: + session(sqlalchemy.session): SQLAlchemy session object. + request(django.request): Django request object. + as_options(bool): Return and select option pairs if True. + cascade(bool): Return subordinate organizations if True. + consultants(bool): Only organizations that can be a consultant if True. + +Returns: + list: Organizations to which the user belongs with subordinate Organizations if cascade. +``` + + +##### `get_resources(self, session, request, of_type=None, cascade=True, for_assigning=False, include_children=True)` \{#appuser-get-resources\} + +```text +Get the resources that the request user is able to assign to clients and consultants. +Args: + session(sqlalchemy.session): SQLAlchemy session object + request(djanog.request): Django request object + of_type(Resource): A subclass of Resource. + cascade(bool): Also retrieve resources of child organizations. + for_assigning(bool): check assign permission versus view permission. + include_children(bool): include the resources that are children to other resources. +Returns: +``` + + +##### `filter_resources(self, resources)` \{#appuser-filter-resources\} + +```text +Filter and sort the resources returned by get_resources. +Args: + resources: all resources that are accessible by this user. + +Returns: + list: list of Resource objects. +``` + + +##### `get_assignable_roles(self, request, as_options=False)` \{#appuser-get-assignable-roles\} + +```text +Get a list of user roles that this user can assign. + +Args: + request: Django request object + as_options: Returns a list of tuple pairs for use as select input options. + +Returns: list of user roles the request user can assign +``` + + +##### `get_peers(self, session, request, include_self=False, cascade=False)` \{#appuser-get-peers\} + +```text +Get AppUsers belonging to organizations to which this user belongs. +Args: + session(sqlalchemy.session): SQLAlchemy session object + request(django.request): Django request object + include_self(bool): Include self in list of users + cascade(bool): Also retrieve resources of child organizations. + +Returns: A list of AppUser objects. +``` + + +##### `update_activity(self, session, request)` \{#appuser-update-activity\} + +```text +Update the is_active status of this user based on the activity of the organizations to which it belongs. +Args: + session(sqlalchemy.session): SQLAlchemy session object + request(django.request): Django request object +``` + + +##### `get_role(self, display_name=False)` \{#appuser-get-role\} + +```text +Get the most elevated role that has been applied to the given user. + +Args: + display_name(bool): Return display friendly name of role if True. + +Returns: Name of role +``` + + +##### `update_permissions(self, session, request, permissions_manager)` \{#appuser-update-permissions\} + +```text +Update custom_permissions of this user based on its role and the licenses of the organizations to which it belongs. +Args: + session(sqlalchemy.session): SQLAlchemy session object. + request(django.request): Django request object. + permissions_manager(AppPermissionsManager): Permissions manager bound to current app. +``` + + +##### `get_rank(self, permissions_manager)` \{#appuser-get-rank\} + +```text +Get the maximum permissions-based rank of the user. +Args: + permissions_manager(AppPermissionsManager): Permissions manager bound to current app. + +Returns: + float: highest permissions-based rank of the user. +``` + + +##### `get_setting(self, session, key, as_value=False, **kwargs)` \{#appuser-get-setting\} + +```text +Get user setting using given criteria. +Args: + session(sqlalchemy.session): database session. + key(str): name of setting. + as_value(bool): return value of setting, instead of UserSetting instance if True. + kwargs: Any number of key value attributes to attach for filtering (i.e.: page, secondary_id, resource). +Returns: + UserSetting: the user setting or None if does not exist. +``` + + +##### `get_all_settings(self, session)` \{#appuser-get-all-settings\} + +```text +Get all user settings. +Args: + session(sqlalchemy.session): database session. +Returns: + list<UserSetting>: All user settings associated with given criteria. +``` + +*`@staticmethod`* + +##### `delete_existing_settings(session, settings)` \{#appuser-delete-existing-settings\} + +```text +Delete all given settings. +Args: + session(sqlalchemy.session): database session. + settings(list<UserSetting>): list of UserSettings to delete. +``` + + +##### `update_setting(self, session, key, value, commit=True, **kwargs)` \{#appuser-update-setting\} + +```text +Update the value of the setting matching the given criteria. +Args: + session(sqlalchemy.session): database session. + key(str): name of setting. + value(str): value of setting. + commit(bool): commit the changes if True. + kwargs: Any number of key value attributes to attach for filtering (i.e.: page, secondary_id, resource). +``` + + +##### `can_view(self, session, request, resource)` \{#appuser-can-view\} + +```text +Check whether this user can view the given resource. +Args: + session(sqlalchemy.session): SQLAlchemy session object + request(django.request): Django request object + resource(Resource): resource to test. + +Returns: + bool: True if user can view the resource, else False. +``` diff --git a/website/docs/api/models/app_users/associations.mdx b/website/docs/api/models/app_users/associations.mdx new file mode 100644 index 00000000..a72576ad --- /dev/null +++ b/website/docs/api/models/app_users/associations.mdx @@ -0,0 +1,11 @@ +--- +id: models.app_users.associations +title: tethysext.atcore.models.app_users.associations +sidebar_label: associations +--- + +# `tethysext.atcore.models.app_users.associations` + +> _No description._ + +_This module exposes no public classes or functions._ diff --git a/website/docs/api/models/app_users/base.mdx b/website/docs/api/models/app_users/base.mdx new file mode 100644 index 00000000..1bf8f490 --- /dev/null +++ b/website/docs/api/models/app_users/base.mdx @@ -0,0 +1,11 @@ +--- +id: models.app_users.base +title: tethysext.atcore.models.app_users.base +sidebar_label: base +--- + +# `tethysext.atcore.models.app_users.base` + +> _No description._ + +_This module exposes no public classes or functions._ diff --git a/website/docs/api/models/app_users/index.mdx b/website/docs/api/models/app_users/index.mdx new file mode 100644 index 00000000..635c99c0 --- /dev/null +++ b/website/docs/api/models/app_users/index.mdx @@ -0,0 +1,24 @@ +--- +id: models.app_users.index +title: tethysext.atcore.models.app_users +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.models.app_users` + +> _No description._ + +## Modules + +- [`app_user`](./app_user.mdx) +- [`associations`](./associations.mdx) +- [`base`](./base.mdx) +- [`initializer`](./initializer.mdx) +- [`organization`](./organization.mdx) +- [`resource`](./resource.mdx) +- [`resource_workflow`](./resource_workflow.mdx) +- [`resource_workflow_result`](./resource_workflow_result.mdx) +- [`resource_workflow_step`](./resource_workflow_step.mdx) +- [`spatial_resource`](./spatial_resource.mdx) +- [`user_setting`](./user_setting.mdx) diff --git a/website/docs/api/models/app_users/initializer.mdx b/website/docs/api/models/app_users/initializer.mdx new file mode 100644 index 00000000..c2957fe0 --- /dev/null +++ b/website/docs/api/models/app_users/initializer.mdx @@ -0,0 +1,24 @@ +--- +id: models.app_users.initializer +title: tethysext.atcore.models.app_users.initializer +sidebar_label: initializer +--- + +# `tethysext.atcore.models.app_users.initializer` + +> _No description._ + +## Functions + + +### `initialize_app_users_db(engine, first_time=False, app_user_model=AppUser)` \{#initialize-app-users-db\} + +```text +Initialize database with app_users tables and initial data. +Args: + engine(sqlalchemy.engine): connection to database. + first_time(bool): is first time initializing. + app_user_model(atcore.models.app_user.AppUser): subclass of AppUser data model. + +Returns: +``` diff --git a/website/docs/api/models/app_users/organization.mdx b/website/docs/api/models/app_users/organization.mdx new file mode 100644 index 00000000..32e48fa3 --- /dev/null +++ b/website/docs/api/models/app_users/organization.mdx @@ -0,0 +1,139 @@ +--- +id: models.app_users.organization +title: tethysext.atcore.models.app_users.organization +sidebar_label: organization +--- + +# `tethysext.atcore.models.app_users.organization` + +> _No description._ + +## Classes + + +### `Organization(AppUsersBase, AttributesMixin)` \{#organization\} + +```text +Definition for organizations table. +``` +#### Methods + +*`@validates('license')`* + +##### `validate_license(self, key, field)` \{#organization-validate-license\} + +> _No description._ + + +*`@staticmethod`* + +##### `get_create_permission()` \{#organization-get-create-permission\} + +```text +Get name of create permission. +Returns: + str: name of create permission for this type of organization. +``` + +*`@staticmethod`* + +##### `get_edit_permission()` \{#organization-get-edit-permission\} + +```text +Get name of edit permission. +Returns: + str: name of edit permission for this type of organization. +``` + +*`@staticmethod`* + +##### `get_delete_permission()` \{#organization-get-delete-permission\} + +```text +Get name of delete permission. +Returns: + str: name of delete permission for this type of organization. +``` + + +##### `get_modify_members_permission(self)` \{#organization-get-modify-members-permission\} + +```text +Get name of modify members permission. +Returns: + str: name of modify members permission for this type of organization. +``` + + +##### `update_member_activity(self, session, request)` \{#organization-update-member-activity\} + +```text +Update the active status of each member. +Args: + session(sqlalchemy.session): SQLAlchemy session object + request(django.request): Django request object +``` + + +##### `can_add_client_with_license(self, session, request, license)` \{#organization-can-add-client-with-license\} + +```text +Determine if this organization can add a new client with the given license. +Args: + session(sqlalchemy.session): SQLAlchemy session object + request(django.request): Django request object + license: valid license. + +Returns: + bool: True if can add client with given license, else False. +``` + + +##### `can_have_clients(self)` \{#organization-can-have-clients\} + +```text +Pass through for LICENSES.can_have_clients. +Returns: + bool: True if can have clients, else false. +``` + + +##### `can_have_consultant(self)` \{#organization-can-have-consultant\} + +```text +Pass through for LICENSES.can_have_consultant. +Returns: + bool: True if can have consultant, else false. +``` + + +##### `must_have_consultant(self)` \{#organization-must-have-consultant\} + +```text +Pass through for LICENSES.must_have_consultant. +Returns: + bool: True if must have consultant, else false. +``` + + +##### `is_member(self, app_user)` \{#organization-is-member\} + +```text +Determine if given app_user is a member of this organization. +Args: + app_user(AppUser): app_user to test. + +Returns: + bool: True when app_user is a member of this organization, else False. +``` + + +## Functions + +*`@event.listens_for(Organization, 'before_delete')`* + +### `receive_before_delete(mapper, connection, target)` \{#receive-before-delete\} + +```text +Handle removal of members and resources that would be orphaned by the removal of the target organization. +``` diff --git a/website/docs/api/models/app_users/resource.mdx b/website/docs/api/models/app_users/resource.mdx new file mode 100644 index 00000000..1b7b3086 --- /dev/null +++ b/website/docs/api/models/app_users/resource.mdx @@ -0,0 +1,45 @@ +--- +id: models.app_users.resource +title: tethysext.atcore.models.app_users.resource +sidebar_label: resource +--- + +# `tethysext.atcore.models.app_users.resource` + +> _No description._ + +## Classes + + +### `Resource(StatusMixin, AttributesMixin, UserLockMixin, SerializeMixin, AppUsersBase)` \{#resource\} + +```text +Definition for the resources table. +``` +#### Methods + + +##### `__repr__(self)` \{#resource-repr\} + +> _No description._ + + +*`@classproperty`* + +##### `SLUG(self)` \{#resource-slug\} + +> _No description._ + + + +##### `serialize_base_fields(self, d: dict) -> dict` \{#resource-serialize-base-fields\} + +```text +Hook for ATCore base classes to add their custom fields to serialization. + +Args: + d: Base serialized Resource dictionary. + +Returns: + Serialized Resource dictionary. +``` diff --git a/website/docs/api/models/app_users/resource_workflow.mdx b/website/docs/api/models/app_users/resource_workflow.mdx new file mode 100644 index 00000000..bbe16429 --- /dev/null +++ b/website/docs/api/models/app_users/resource_workflow.mdx @@ -0,0 +1,189 @@ +--- +id: models.app_users.resource_workflow +title: tethysext.atcore.models.app_users.resource_workflow +sidebar_label: resource_workflow +--- + +# `tethysext.atcore.models.app_users.resource_workflow` + +```text +******************************************************************************** +* Name: resource_workflow.py +* Author: nswain +* Created On: September 25, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflow(AppUsersBase, AttributesMixin, ResultsMixin, UserLockMixin, SerializeMixin)` \{#resourceworkflow\} + +```text +Data model for storing information about resource workflows. + +Primary Workflow Status Progression: +1. STATUS_PENDING = No steps have been started in workflow. +2. STATUS_CONTINUE = Workflow has non-complete steps, has no steps with errors or failed, and no steps that are processing. +3. STATUS_WORKING = Workflow has steps that are processing. +4. STATUS_ERROR = Workflow has steps with errors.test_get_status_options_list +5. STATUS_FAILED = Workflow has steps that have failed. +6. STATUS_COMPLETE = All steps are complete in workflow. + +Review Workflow Status Progression (if applicable): +1. STATUS_SUBMITTED = Workflow submitted for review +2. STATUS_UNDER_REVIEW = Workflow currently being reviewed +3a. STATUS_APPROVED = Changes approved. +3b. STATUS_REJECTED = Changes disapproved. +3c. STATUS_CHANGES_REQUESTED = Changes required and resubmit +``` +#### Methods + + +##### `__repr__(self)` \{#resourceworkflow-repr\} + +> _No description._ + + +*`@property`* + +##### `complete(self)` \{#resourceworkflow-complete\} + +> _No description._ + + + +##### `get_next_step(self)` \{#resourceworkflow-get-next-step\} + +```text +Return the next step object, based on the status of the steps. + +Returns: + int, ResourceWorkflowStep: the index of the next step and the next step. +``` + + +##### `get_status(self)` \{#resourceworkflow-get-status\} + +```text +Returns the status of the next workflow step. + +Returns: + ResourceWorkflowStep.STATUS_X: status of the next step. +``` + + +##### `get_step_by_name(self, name)` \{#resourceworkflow-get-step-by-name\} + +```text +Get the step from the workflow with given name. + +Args: + name(str): The name of the step you want to get. + +Returns: + ResourceWorkflowStep: the step with matching name or None if not found. +``` + + +##### `get_adjacent_steps(self, step)` \{#resourceworkflow-get-adjacent-steps\} + +```text +Get the adjacent steps to the given step. + +Args: + step(ResourceWorkflowStep): A step belonging to this workflow. + +Returns: + ResourceWorkflowStep, ResourceWorkflowStep: previous and next steps, respectively. +``` + + +##### `get_previous_steps(self, step)` \{#resourceworkflow-get-previous-steps\} + +```text +Get all previous steps to the given step. + +Args: + step(ResourceWorkflowStep): A step belonging to this workflow. + +Returns: + list<ResourceWorkflowStep>: a list of steps previous to this one. +``` + + +##### `get_tabular_data_for_previous_steps(self, step, request, session, resource)` \{#resourceworkflow-get-tabular-data-for-previous-steps\} + +```text +Get all tabular data for previous steps based on the given step. + +Args: + step(ResourceWorkflowStep): A step belonging to this workflow. + request(HttpRequest): The request. + session(sqlalchemy.orm.Session): Session bound to the steps. + resource(Resource): the resource for this request. + +Returns: + dict: a dictionary with tabular data per step. +``` + + +##### `get_next_steps(self, step)` \{#resourceworkflow-get-next-steps\} + +```text +Get all steps following the given step. +Args: + step(ResourceWorkflowStep): A step belonging to this workflow. + +Returns: + list<ResourceWorkflowStep>: a list of steps following this one. +``` + + +##### `reset_next_steps(self, step, include_current=False)` \{#resourceworkflow-reset-next-steps\} + +```text +Reset all steps following the given step that are not PENDING. +Args: + step(ResourceWorkflowStep): A step belonging to this workflow. + include_current(bool): Reset current step +``` + +*`@abstractmethod`* + +##### `get_url_name(self) -> str` \{#resourceworkflow-get-url-name\} + +```text +Override to specify the URL name for the workflow type. +``` + + +##### `get_url(self)` \{#resourceworkflow-get-url\} + +```text +Get the URL to the workflow. IMPORTANT: Must implement get_url_name(). +``` + +*`@staticmethod`* + +##### `get_key_from_value(dict_object, value)` \{#resourceworkflow-get-key-from-value\} + +```text +Get the key from a given value +dict_object: dictionary object +value: value to look up +:return: key associated with the value +``` + + +##### `serialize_base_fields(self, d: dict) -> dict` \{#resourceworkflow-serialize-base-fields\} + +```text +Hook for ATCore base classes to add their custom fields to serialization. + +Args: + d: Base serialized Resource dictionary. + +Returns: + Serialized Resource dictionary. +``` diff --git a/website/docs/api/models/app_users/resource_workflow_result.mdx b/website/docs/api/models/app_users/resource_workflow_result.mdx new file mode 100644 index 00000000..53c76a46 --- /dev/null +++ b/website/docs/api/models/app_users/resource_workflow_result.mdx @@ -0,0 +1,84 @@ +--- +id: models.app_users.resource_workflow_result +title: tethysext.atcore.models.app_users.resource_workflow_result +sidebar_label: resource_workflow_result +--- + +# `tethysext.atcore.models.app_users.resource_workflow_result` + +```text +******************************************************************************** +* Name: resource_workflow_result +* Author: nswain +* Created On: April 30, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflowResult(AppUsersBase, StatusMixin, AttributesMixin, OptionsMixin, SerializeMixin)` \{#resourceworkflowresult\} + +```text +Data model for storing information about resource workflow results. +``` +#### Methods + + +##### `__init__(self, *args, **kwargs)` \{#resourceworkflowresult-init\} + +> _No description._ + + + +##### `__str__(self)` \{#resourceworkflowresult-str\} + +> _No description._ + + + +##### `__repr__(self)` \{#resourceworkflowresult-repr\} + +> _No description._ + + +*`@property`* + +##### `controller(self)` \{#resourceworkflowresult-controller\} + +> _No description._ + + +*`@property`* + +##### `data(self)` \{#resourceworkflowresult-data\} + +> _No description._ + + +*`@data.setter`* + +##### `data(self, value)` \{#resourceworkflowresult-data\} + +> _No description._ + + + +##### `reset(self)` \{#resourceworkflowresult-reset\} + +```text +Resets result to initial state. +``` + + +##### `serialize_base_fields(self, d: dict) -> dict` \{#resourceworkflowresult-serialize-base-fields\} + +```text +Hook for ATCore base classes to add their custom fields to serialization. + +Args: + d: Base serialized Resource dictionary. + +Returns: + Serialized Resource dictionary. +``` diff --git a/website/docs/api/models/app_users/resource_workflow_step.mdx b/website/docs/api/models/app_users/resource_workflow_step.mdx new file mode 100644 index 00000000..0eee404c --- /dev/null +++ b/website/docs/api/models/app_users/resource_workflow_step.mdx @@ -0,0 +1,217 @@ +--- +id: models.app_users.resource_workflow_step +title: tethysext.atcore.models.app_users.resource_workflow_step +sidebar_label: resource_workflow_step +--- + +# `tethysext.atcore.models.app_users.resource_workflow_step` + +```text +******************************************************************************** +* Name: resource_workflow_step +* Author: nswain +* Created On: November 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflowStep(AppUsersBase, StatusMixin, AttributesMixin, OptionsMixin)` \{#resourceworkflowstep\} + +```text +Data model for storing information about resource workflows. + +Primary Workflow Step Status Progression: +1. STATUS_PENDING = Step has not been started yet. +2. STATUS_WORKING = Processing on step has been started but not complete. +3. STATUS_ERROR = ValueError or ValidateError has occurred. +4. STATUS_FAILED = Processing error has occurred. +5. STATUS_COMPLETE = Step has been completed successfully. + +Review Workflow Status Progression (if applicable): +1. STATUS_SUBMITTED = Workflow submitted for review +2. STATUS_UNDER_REVIEW = Workflow currently being reviewed +3a. STATUS_APPROVED = Changes approved. +3b. STATUS_REJECTED = Changes disapproved. +3c. STATUS_CHANGES_REQUESTED = Changes required and resubmit + +Options: + workflow_lock_required(bool): This step requires a workflow lock to be performed (to prevent conflicts). Defaults to False. + release_workflow_lock_on_completion(bool): Attempt to release a workflow lock when the view is initialized. Defaults to True. + release_workflow_lock_on_init(bool): Attempt to release a workflow lock when the step is completed. Ignored if `workflow_lock_required` is True. Defaults to False. + resource_lock_required(bool): This step requires a resource lock to be performed (to prevent conflicts). Defaults to False. + release_resource_lock_on_completion(bool): Attempt to release a resource lock when the view is initialized. Defaults to True. + release_resource_lock_on_init(bool): Attempt to release a resource lock when the step is completed. Ignored if `resource_lock_required` is True. Defaults to False. +``` +#### Methods + + +##### `__init__(self, *args, **kwargs)` \{#resourceworkflowstep-init\} + +> _No description._ + + + +##### `__str__(self)` \{#resourceworkflowstep-str\} + +> _No description._ + + + +##### `__repr__(self)` \{#resourceworkflowstep-repr\} + +> _No description._ + + +*`@property`* + +##### `default_options(self)` \{#resourceworkflowstep-default-options\} + +```text +Returns default options dictionary for the object. +``` + +*`@property`* + +##### `complete(self)` \{#resourceworkflowstep-complete\} + +> _No description._ + + +*`@property`* + +##### `active_roles(self)` \{#resourceworkflowstep-active-roles\} + +> _No description._ + + +*`@active_roles.setter`* + +##### `active_roles(self, value)` \{#resourceworkflowstep-active-roles\} + +> _No description._ + + +*`@property`* + +##### `controller(self)` \{#resourceworkflowstep-controller\} + +> _No description._ + + +*`@abstractmethod`* + +##### `init_parameters(self, *args, **kwargs)` \{#resourceworkflowstep-init-parameters\} + +```text +Initialize the parameters for this step. +Returns: + dict<name:dict<help,value>>: Dictionary of all parameters with their initial value set. +``` + +*`@classmethod`* + +##### `valid_statuses(cls)` \{#resourceworkflowstep-valid-statuses\} + +```text +Primary Workflow Step Status Progression: +1. STATUS_PENDING = Step has not been started yet. +2. STATUS_WORKING = Processing on step has been started but not complete. +3. STATUS_ERROR = ValueError or ValidateError has occurred. +4. STATUS_FAILED = Processing error has occurred. +5. STATUS_COMPLETE = Step has been completed successfully. + +Review Workflow Status Progression (if applicable): +1. STATUS_SUBMITTED = Workflow submitted for review. +2. STATUS_UNDER_REVIEW = Workflow currently being reviewed. +3a. STATUS_APPROVED = Changes approved. +3b. STATUS_REJECTED = Changes disapproved. +3c. STATUS_CHANGES_REQUESTED = Changes required and resubmit. +4. STATUS_REVIEWED - Workflow has been reviewed. + +Returns: + list: valid statuses. +``` + + +##### `to_dict(self)` \{#resourceworkflowstep-to-dict\} + +```text +Serialize ResourceWorkflowStep into a dictionary. + +Returns: + dict: dictionary representation of ResourceWorkflowStep. +``` + + +##### `to_json(self)` \{#resourceworkflowstep-to-json\} + +```text +Serialize ResourceWorkflowStep, including parameters, to json. + +Returns: + str: JSON string representation of ResourceWorkflowStep. +``` + + +##### `validate(self)` \{#resourceworkflowstep-validate\} + +```text +Validates parameter values of this this step. If the parameter values of this step are invalid a ValueError will be raised +``` + + +##### `parse_parameters(self, parameters)` \{#resourceworkflowstep-parse-parameters\} + +```text +Parse parameters from a dictionary. + +Args: + parameters(dict<name,value>): Dictionary of parameters. +``` + + +##### `set_parameter(self, name, value)` \{#resourceworkflowstep-set-parameter\} + +```text +Sets the value of the named parameter. +Args: + name(str): Name of the parameter to set. + value(varies): Value of the parameter. +``` + + +##### `get_parameter(self, name)` \{#resourceworkflowstep-get-parameter\} + +```text +Get value of the named parameter. +Args: + name(str): name of parameter. + +Returns: + varies: Value of the named parameter. +``` + + +##### `get_parameters(self)` \{#resourceworkflowstep-get-parameters\} + +```text +Get all parameter objects. +Returns: + dict<name:dict<help,value>>: Dictionary of all parameters with their initial value set. +``` + + +##### `resolve_option(self, option)` \{#resourceworkflowstep-resolve-option\} + +```text +Resolve options that depend on parameters from other steps. +``` + + +##### `reset(self)` \{#resourceworkflowstep-reset\} + +```text +Resets the step back to its initial state. +``` diff --git a/website/docs/api/models/app_users/spatial_resource.mdx b/website/docs/api/models/app_users/spatial_resource.mdx new file mode 100644 index 00000000..782fa9eb --- /dev/null +++ b/website/docs/api/models/app_users/spatial_resource.mdx @@ -0,0 +1,51 @@ +--- +id: models.app_users.spatial_resource +title: tethysext.atcore.models.app_users.spatial_resource +sidebar_label: spatial_resource +--- + +# `tethysext.atcore.models.app_users.spatial_resource` + +```text +******************************************************************************** +* Name: spatial_resource.py +* Author: glarsen +* Created On: November 17, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `SpatialResource(Resource)` \{#spatialresource\} + +> _No description._ + +#### Methods + + +##### `set_extent(self, obj: Union[dict, str], object_format: str='dict', srid=4326)` \{#spatialresource-set-extent\} + +```text +Set the extent for the SpatialResource. + +Args: + obj: A string or a dict representing the extent. + object_format (str): A string defining the type of obj. One of 'wkt', 'geojson', and 'dict'. + srid(str): EPSG code of the extent. +``` + + +##### `get_extent(self, extent_type: str='dict')` \{#spatialresource-get-extent\} + +```text +Get the extent from the SpatialResource. + +Args: + extent_type (str): The format that should be returned for the extent. One of 'wkt', 'geojson', and 'dict'. +``` + + +##### `update_extent_srid(self, srid)` \{#spatialresource-update-extent-srid\} + +> _No description._ diff --git a/website/docs/api/models/app_users/user_setting.mdx b/website/docs/api/models/app_users/user_setting.mdx new file mode 100644 index 00000000..344ad32c --- /dev/null +++ b/website/docs/api/models/app_users/user_setting.mdx @@ -0,0 +1,24 @@ +--- +id: models.app_users.user_setting +title: tethysext.atcore.models.app_users.user_setting +sidebar_label: user_setting +--- + +# `tethysext.atcore.models.app_users.user_setting` + +```text +******************************************************************************** +* Name: user_setting.py +* Author: nswain +* Created On: April 18, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `UserSetting(AttributesMixin, AppUsersBase)` \{#usersetting\} + +```text +SQLAlchemy interface for user_settings table. +``` diff --git a/website/docs/api/models/controller_metadata.mdx b/website/docs/api/models/controller_metadata.mdx new file mode 100644 index 00000000..b8bb8cfa --- /dev/null +++ b/website/docs/api/models/controller_metadata.mdx @@ -0,0 +1,38 @@ +--- +id: models.controller_metadata +title: tethysext.atcore.models.controller_metadata +sidebar_label: controller_metadata +--- + +# `tethysext.atcore.models.controller_metadata` + +```text +******************************************************************************** +* Name: controller_metadata +* Author: nswain +* Created On: April 18, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `ControllerMetadata(AppUsersBase)` \{#controllermetadata\} + +```text +Data model that stores controller metadata for objects associated with controllers. +``` +#### Methods + + +##### `instantiate(self, **kwargs)` \{#controllermetadata-instantiate\} + +```text +Instantiate an instance of the TethysController referenced by the path with the given kwargs. + +Args: + kwargs: any kwargs that would be passed to the as_controller method of TethysControllers (i.e.: class-based view property overrides). + +Returns: + function: the controller method. +``` diff --git a/website/docs/api/models/file_database/_category_.json b/website/docs/api/models/file_database/_category_.json new file mode 100644 index 00000000..db5712d5 --- /dev/null +++ b/website/docs/api/models/file_database/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "file_database", + "position": 19 +} diff --git a/website/docs/api/models/file_database/file_collection.mdx b/website/docs/api/models/file_database/file_collection.mdx new file mode 100644 index 00000000..31768ab8 --- /dev/null +++ b/website/docs/api/models/file_database/file_collection.mdx @@ -0,0 +1,24 @@ +--- +id: models.file_database.file_collection +title: tethysext.atcore.models.file_database.file_collection +sidebar_label: file_collection +--- + +# `tethysext.atcore.models.file_database.file_collection` + +```text +******************************************************************************** +* Name: file_collection.py +* Author: glarsen +* Created On: October 30, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `FileCollection(AppUsersBase)` \{#filecollection\} + +```text +A model representing a FileCollection +``` diff --git a/website/docs/api/models/file_database/file_database-module.mdx b/website/docs/api/models/file_database/file_database-module.mdx new file mode 100644 index 00000000..23d19ed0 --- /dev/null +++ b/website/docs/api/models/file_database/file_database-module.mdx @@ -0,0 +1,24 @@ +--- +id: models.file_database.file_database +title: tethysext.atcore.models.file_database.file_database +sidebar_label: file_database +--- + +# `tethysext.atcore.models.file_database.file_database` + +```text +******************************************************************************** +* Name: file_database.py +* Author: glarsen +* Created On: October 30, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `FileDatabase(AppUsersBase)` \{#filedatabase\} + +```text +A model representing a FileDatabase +``` diff --git a/website/docs/api/models/file_database/index.mdx b/website/docs/api/models/file_database/index.mdx new file mode 100644 index 00000000..2777831f --- /dev/null +++ b/website/docs/api/models/file_database/index.mdx @@ -0,0 +1,22 @@ +--- +id: models.file_database.index +title: tethysext.atcore.models.file_database +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.models.file_database` + +```text +******************************************************************************** +* Name: file_database +* Author: glarsen +* Created On: October 30, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Modules + +- [`file_collection`](./file_collection.mdx) +- [`file_database-module`](./file_database-module.mdx) +- [`resource_file_collection_association`](./resource_file_collection_association.mdx) diff --git a/website/docs/api/models/file_database/resource_file_collection_association.mdx b/website/docs/api/models/file_database/resource_file_collection_association.mdx new file mode 100644 index 00000000..fa13fc17 --- /dev/null +++ b/website/docs/api/models/file_database/resource_file_collection_association.mdx @@ -0,0 +1,11 @@ +--- +id: models.file_database.resource_file_collection_association +title: tethysext.atcore.models.file_database.resource_file_collection_association +sidebar_label: resource_file_collection_association +--- + +# `tethysext.atcore.models.file_database.resource_file_collection_association` + +> _No description._ + +_This module exposes no public classes or functions._ diff --git a/website/docs/api/models/index.mdx b/website/docs/api/models/index.mdx new file mode 100644 index 00000000..20d13caa --- /dev/null +++ b/website/docs/api/models/index.mdx @@ -0,0 +1,19 @@ +--- +id: models.index +title: tethysext.atcore.models +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.models` + +> _No description._ + +## Modules + +- [`app_users`](./app_users/index.mdx) +- [`controller_metadata`](./controller_metadata.mdx) +- [`file_database`](./file_database/index.mdx) +- [`resource_workflow_results`](./resource_workflow_results/index.mdx) +- [`resource_workflow_steps`](./resource_workflow_steps/index.mdx) +- [`types`](./types/index.mdx) diff --git a/website/docs/api/models/resource_workflow_results/_category_.json b/website/docs/api/models/resource_workflow_results/_category_.json new file mode 100644 index 00000000..b44c3bd6 --- /dev/null +++ b/website/docs/api/models/resource_workflow_results/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "resource_workflow_results", + "position": 20 +} diff --git a/website/docs/api/models/resource_workflow_results/dataset_workflow_result.mdx b/website/docs/api/models/resource_workflow_results/dataset_workflow_result.mdx new file mode 100644 index 00000000..034291cc --- /dev/null +++ b/website/docs/api/models/resource_workflow_results/dataset_workflow_result.mdx @@ -0,0 +1,74 @@ +--- +id: models.resource_workflow_results.dataset_workflow_result +title: tethysext.atcore.models.resource_workflow_results.dataset_workflow_result +sidebar_label: dataset_workflow_result +--- + +# `tethysext.atcore.models.resource_workflow_results.dataset_workflow_result` + +```text +******************************************************************************** +* Name: dataset_workflow_result.py +* Author: nswain +* Created On: June 3, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `DatasetWorkflowResult(ResourceWorkflowResult)` \{#datasetworkflowresult\} + +```text +Data model for storing spatial information about resource workflow results. +``` +#### Methods + + +##### `__init__(self, *args, **kwargs)` \{#datasetworkflowresult-init\} + +```text +Constructor. + +Args: +``` + +*`@property`* + +##### `default_options(self)` \{#datasetworkflowresult-default-options\} + +```text +Returns default options dictionary for the object. +``` + +*`@property`* + +##### `datasets(self)` \{#datasetworkflowresult-datasets\} + +> _No description._ + + +*`@datasets.setter`* + +##### `datasets(self, value)` \{#datasetworkflowresult-datasets\} + +> _No description._ + + + +##### `reset(self)` \{#datasetworkflowresult-reset\} + +> _No description._ + + + +##### `add_pandas_dataframe(self, title, data_frame, show_export_button=False)` \{#datasetworkflowresult-add-pandas-dataframe\} + +```text +Adds a pandas.DataFrame to the result. + +Args: + title(str): Display name. + data_frame(pandas.DataFrame): The data. + show_export_button(boolean): Enable data export option. +``` diff --git a/website/docs/api/models/resource_workflow_results/index.mdx b/website/docs/api/models/resource_workflow_results/index.mdx new file mode 100644 index 00000000..2a188269 --- /dev/null +++ b/website/docs/api/models/resource_workflow_results/index.mdx @@ -0,0 +1,23 @@ +--- +id: models.resource_workflow_results.index +title: tethysext.atcore.models.resource_workflow_results +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.models.resource_workflow_results` + +```text +******************************************************************************** +* Name: resource_workflow_results +* Author: nswain +* Created On: May 16, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Modules + +- [`dataset_workflow_result`](./dataset_workflow_result.mdx) +- [`plot_workflow_result`](./plot_workflow_result.mdx) +- [`report_workflow_result`](./report_workflow_result.mdx) +- [`spatial_workflow_result`](./spatial_workflow_result.mdx) diff --git a/website/docs/api/models/resource_workflow_results/plot_workflow_result.mdx b/website/docs/api/models/resource_workflow_results/plot_workflow_result.mdx new file mode 100644 index 00000000..da2809e4 --- /dev/null +++ b/website/docs/api/models/resource_workflow_results/plot_workflow_result.mdx @@ -0,0 +1,128 @@ +--- +id: models.resource_workflow_results.plot_workflow_result +title: tethysext.atcore.models.resource_workflow_results.plot_workflow_result +sidebar_label: plot_workflow_result +--- + +# `tethysext.atcore.models.resource_workflow_results.plot_workflow_result` + +```text +******************************************************************************** +* Name: plot_workflow_result.py +* Author: nathan, htran, msouff +* Created On: Oct 7, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `PlotWorkflowResult(ResourceWorkflowResult)` \{#plotworkflowresult\} + +```text +Data model for storing spatial information about resource workflow results. + +Options: + renderer (str): bokeh or plotly + axes(list): A list of tuples for pair axis ex. For example: [('x', 'y'), ('x1', 'y1'), ('x', 'y2')] + axis_labels(list): A list of label for x and y axes respectively. For example: ['x', 'y'] + plot_type (str): lines, scatter, or bar + line_shape (str): Only for plotly. You can select from on of these options: linear, spline, vhv, hvh, vh, hv + x_axis_type (str): type of x axis. Available options are 'linear' or 'datetime' +``` +#### Methods + + +##### `__init__(self, *args, **kwargs)` \{#plotworkflowresult-init\} + +```text +Constructor. + +Args: +``` + +*`@property`* + +##### `default_options(self)` \{#plotworkflowresult-default-options\} + +```text +Returns default options dictionary for the object. +``` + +*`@property`* + +##### `datasets(self)` \{#plotworkflowresult-datasets\} + +> _No description._ + + +*`@datasets.setter`* + +##### `datasets(self, value)` \{#plotworkflowresult-datasets\} + +> _No description._ + + +*`@property`* + +##### `plot(self)` \{#plotworkflowresult-plot\} + +> _No description._ + + +*`@plot.setter`* + +##### `plot(self, value)` \{#plotworkflowresult-plot\} + +> _No description._ + + + +##### `reset(self)` \{#plotworkflowresult-reset\} + +> _No description._ + + + +##### `add_series(self, title, data)` \{#plotworkflowresult-add-series\} + +```text +Add plot series into plot dataset. We assume that the first column is x and second column is y + +Args: + title: series name + data: plot data. Support different types of data: 2-D list, 2-D Numpy array and pandas dataframe with 2 col. +``` + + +##### `plot_from_dataframe(self, data_frame, series_axes=None, series_labels=None)` \{#plotworkflowresult-plot-from-dataframe\} + +```text +Adds a pandas.DataFrame with multiple columns to the result. + +Args: + data_frame(pandas.DataFrame): The data. + series_axes(list): A list of tuple label for x and y axes respectively. + For example: [('x', 'y'), ('x1', 'y1)]. if plot axes is not provided, + the first column is x and the rest are ys. + series_labels(list): A list of series' label. For example: ['Series 1', 'Series 2', 'Series 3']. +``` + + +##### `add_plot(self, plot)` \{#plotworkflowresult-add-plot\} + +```text +Adds a plotly plot object to the result. + +Args: + plot(obj): plotly figure. Only support adding one plot. +``` + + +##### `get_plot_object(self)` \{#plotworkflowresult-get-plot-object\} + +```text +Gets plot object from the result. + +Returns plot object. +``` diff --git a/website/docs/api/models/resource_workflow_results/report_workflow_result.mdx b/website/docs/api/models/resource_workflow_results/report_workflow_result.mdx new file mode 100644 index 00000000..3e3709ee --- /dev/null +++ b/website/docs/api/models/resource_workflow_results/report_workflow_result.mdx @@ -0,0 +1,45 @@ +--- +id: models.resource_workflow_results.report_workflow_result +title: tethysext.atcore.models.resource_workflow_results.report_workflow_result +sidebar_label: report_workflow_result +--- + +# `tethysext.atcore.models.resource_workflow_results.report_workflow_result` + +```text +******************************************************************************** +* Name: spatial_workflow_result +* Author: nswain +* Created On: April 30, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `ReportWorkflowResult(ResourceWorkflowResult)` \{#reportworkflowresult\} + +```text +Data model for storing spatial information about resource workflow results. +``` +#### Methods + + +##### `__init__(self, geoserver_name, map_manager, spatial_manager, map_renderer='tethys_map_view', *args, **kwargs)` \{#reportworkflowresult-init\} + +```text +Constructor. + +Args: + geoserver_name(str): Name of geoserver setting to use. + map_manager(MapManager): Instance of MapManager to use for the map view. + spatial_manager(SpatialManager): Instance of SpatialManager to use for the map view. +``` + +*`@property`* + +##### `default_options(self)` \{#reportworkflowresult-default-options\} + +```text +Returns default options dictionary for the object. +``` diff --git a/website/docs/api/models/resource_workflow_results/spatial_workflow_result.mdx b/website/docs/api/models/resource_workflow_results/spatial_workflow_result.mdx new file mode 100644 index 00000000..4b230dfe --- /dev/null +++ b/website/docs/api/models/resource_workflow_results/spatial_workflow_result.mdx @@ -0,0 +1,175 @@ +--- +id: models.resource_workflow_results.spatial_workflow_result +title: tethysext.atcore.models.resource_workflow_results.spatial_workflow_result +sidebar_label: spatial_workflow_result +--- + +# `tethysext.atcore.models.resource_workflow_results.spatial_workflow_result` + +```text +******************************************************************************** +* Name: spatial_workflow_result +* Author: nswain +* Created On: April 30, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialWorkflowResult(ResourceWorkflowResult)` \{#spatialworkflowresult\} + +```text +Data model for storing spatial information about resource workflow results. +``` +#### Methods + + +##### `__init__(self, geoserver_name, map_manager, spatial_manager, map_renderer='tethys_map_view', *args, **kwargs)` \{#spatialworkflowresult-init\} + +```text +Constructor. + +Args: + geoserver_name(str): Name of geoserver setting to use. + map_manager(MapManager): Instance of MapManager to use for the map view. + spatial_manager(SpatialManager): Instance of SpatialManager to use for the map view. +``` + +*`@property`* + +##### `default_options(self)` \{#spatialworkflowresult-default-options\} + +```text +Returns default options dictionary for the object. +``` + +*`@property`* + +##### `layers(self)` \{#spatialworkflowresult-layers\} + +> _No description._ + + +*`@layers.setter`* + +##### `layers(self, value)` \{#spatialworkflowresult-layers\} + +> _No description._ + + + +##### `get_layer(self, layer_id)` \{#spatialworkflowresult-get-layer\} + +```text +Get layer with given identifier. If layer has layer_id attribute it will be checked, otherwise the layer_name attribute will be checked. + +Args: + layer_id: Identifier of layer (either layer_name or layer_id). + +Returns: + dict: Layer dictionary. +``` + + +##### `reset(self)` \{#spatialworkflowresult-reset\} + +> _No description._ + + + +##### `add_geojson_layer(self, geojson, layer_name, layer_title, layer_variable, layer_id='', visible=True, public=True, selectable=False, plottable=False, has_action=False, extent=None, popup_title=None, excluded_properties=None, show_download=False, label_options=None)` \{#spatialworkflowresult-add-geojson-layer\} + +```text +Add a geojson layer to display on the map of this result view. + +Args: + geojson(dict): Python equivalent GeoJSON FeatureCollection. + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + show_download(boolean): enable download layer to shapefile. + label_options(dict): Dictionary for labeling. Possibilities include label_property (the name of the + property to label), font (label font), text_align (alignment of the label), offset_x (x offset). Optional. +``` + + +##### `add_wms_layer(self, endpoint, layer_name, layer_title, layer_variable, layer_id='', viewparams=None, env=None, visible=True, public=True, tiled=True, selectable=False, plottable=False, has_action=False, extent=None, popup_title=None, excluded_properties=None, geometry_attribute='geometry', use_geoserver_legend=False, geoserver_legend_params=None, color_ramp_division_kwargs=None, times=None)` \{#spatialworkflowresult-add-wms-layer\} + +```text +Add a wms layer to display on the map of this result view. + +Args: + endpoint(str): URL to GeoServer WMS interface. + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + viewparams(str): VIEWPARAMS string. + env(str): ENV string. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + tiled(bool): Configure as tiled layer if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + geometry_attribute(str): Name of the geometry attribute. Defaults to "geometry". + use_geoserver_legend(bool): If True, the legend will be retrieved directly from Geoserver using GetLegendGraphic request, + If False,a legend will be generated locally using the parameter `color_ramp_division_kwargs`. + geoserver_legend_params: Dictionary of additional GeoServer GetLegendGraphic request parameters. + Both standard WMS parameters (e.g. "transparent", "format") and legend-specific options ("legend_options") can be included. + Example: + { + "transparent": "true", + "format": "image/png", + "legend_options": "hideEmptyRules:true;fontSize:12" + } + color_ramp_division_kwargs(dict): arguments from map_manager.generate_custom_color_ramp_divisions + times (list): List of time steps if layer is time-enabled. Times should be represented as strings in ISO 8601 format (e.g.: ["20210322T112511Z", "20210322T122511Z", "20210322T132511Z"]). Currently only supported in CesiumMapView. +``` + + +##### `add_cesium_layer(self, cesium_type, cesium_json, layer_name, layer_title, layer_variable, layer_id='', visible=True, public=True, selectable=False, plottable=False, has_action=False, extent=None, popup_title=None, excluded_properties=None, show_download=False)` \{#spatialworkflowresult-add-cesium-layer\} + +```text +Add a geojson layer to display on the map of this result view. + +Args: + cesium_type(enum): 'CesiumModel' or 'CesiumPrimitive' + cesium_json(dict): Cesium object in json. + layer_name(str): Name of cesium layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + show_download(boolean): enable download layer to shapefile. +``` + + +##### `update_layer(self, update_layer)` \{#spatialworkflowresult-update-layer\} + +```text +Update color ramp for layer. + +Args: + update_layer: layer to update. +``` diff --git a/website/docs/api/models/resource_workflow_steps/_category_.json b/website/docs/api/models/resource_workflow_steps/_category_.json new file mode 100644 index 00000000..0eacbdab --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "resource_workflow_steps", + "position": 21 +} diff --git a/website/docs/api/models/resource_workflow_steps/form_input_rws.mdx b/website/docs/api/models/resource_workflow_steps/form_input_rws.mdx new file mode 100644 index 00000000..84fc09f6 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/form_input_rws.mdx @@ -0,0 +1,80 @@ +--- +id: models.resource_workflow_steps.form_input_rws +title: tethysext.atcore.models.resource_workflow_steps.form_input_rws +sidebar_label: form_input_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.form_input_rws` + +```text +******************************************************************************** +* Name: form_input_rws.py +* Author: glarsen, mlebaron +* Created On: October 17, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `FormInputRWS(ResourceWorkflowStep)` \{#forminputrws\} + +```text +Workflow step that can be used to get form input from a user. + +Options: + form_title(str): Title to be displayed at the top of the form. Defaults to the name of the step. + status_label(str): Custom label for the status select form field. Defaults to "Status". + param_class(dict): A param class to represent form fields. + renderer(str): Renderer option. Available values are 'django' and 'bokeh'. Defauls to 'django'. + validators (dict, optional): A dictionary of validator functions to check parameter values + before running the tool. Validators are called automatically when a parameter is set. + The structure can be: + + { + 'param_name': validator_func, # Single parameter + ('param_name_1', 'param_name_2'): validator_func # Multiple parameters + } + + Where `validator_func` is a callable that receives the parameter value(s) and raises a `ValueError` + if the value is invalid. + + Examples: + + # Validate a single parameter + def validate_start(value): + if value < 0: + raise ValueError("Start value must be non-negative") + + validators = { + 'start_time': validate_start + } + + # Validate multiple parameters together + def validate_range(start, end): + if start >= end: + raise ValueError("Start must be earlier than end") + + validators = { + ('start_time', 'end_time'): validate_range + } +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#forminputrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#forminputrws-init-parameters\} + +> _No description._ + + + +##### `validate(self)` \{#forminputrws-validate\} + +> _No description._ diff --git a/website/docs/api/models/resource_workflow_steps/index.mdx b/website/docs/api/models/resource_workflow_steps/index.mdx new file mode 100644 index 00000000..8d56e09c --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/index.mdx @@ -0,0 +1,29 @@ +--- +id: models.resource_workflow_steps.index +title: tethysext.atcore.models.resource_workflow_steps +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.models.resource_workflow_steps` + +```text +******************************************************************************** +* Name: resource_workflow_steps +* Author: nswain +* Created On: December 17, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Modules + +- [`form_input_rws`](./form_input_rws.mdx) +- [`results_rws`](./results_rws.mdx) +- [`set_status_rws`](./set_status_rws.mdx) +- [`spatial_attributes_rws`](./spatial_attributes_rws.mdx) +- [`spatial_condor_job_rws`](./spatial_condor_job_rws.mdx) +- [`spatial_dataset_rws`](./spatial_dataset_rws.mdx) +- [`spatial_input_rws`](./spatial_input_rws.mdx) +- [`spatial_rws`](./spatial_rws.mdx) +- [`table_input_rws`](./table_input_rws.mdx) +- [`xms_tool_rws`](./xms_tool_rws.mdx) diff --git a/website/docs/api/models/resource_workflow_steps/results_rws.mdx b/website/docs/api/models/resource_workflow_steps/results_rws.mdx new file mode 100644 index 00000000..68b21f38 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/results_rws.mdx @@ -0,0 +1,49 @@ +--- +id: models.resource_workflow_steps.results_rws +title: tethysext.atcore.models.resource_workflow_steps.results_rws +sidebar_label: results_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.results_rws` + +```text +******************************************************************************** +* Name: results_rws.py +* Author: nswain +* Created On: March 28, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `ResultsResourceWorkflowStep(ResourceWorkflowStep, AttributesMixin, ResultsMixin)` \{#resultsresourceworkflowstep\} + +```text +Abstract base class of all Results Resource Workflow Steps. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#resultsresourceworkflowstep-default-options\} + +```text +Returns default options dictionary for the result. +``` + + +##### `init_parameters(self, *args, **kwargs)` \{#resultsresourceworkflowstep-init-parameters\} + +```text +Initialize the parameters for this step. +Returns: + dict<name:dict<help,value>>: Dictionary of all parameters with their initial value set. +``` + + +##### `reset(self)` \{#resultsresourceworkflowstep-reset\} + +```text +Resets the step back to its initial state. +``` diff --git a/website/docs/api/models/resource_workflow_steps/set_status_rws.mdx b/website/docs/api/models/resource_workflow_steps/set_status_rws.mdx new file mode 100644 index 00000000..b5090fe2 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/set_status_rws.mdx @@ -0,0 +1,57 @@ +--- +id: models.resource_workflow_steps.set_status_rws +title: tethysext.atcore.models.resource_workflow_steps.set_status_rws +sidebar_label: set_status_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.set_status_rws` + +```text +******************************************************************************** +* Name: set_status_rws.py +* Author: nswain +* Created On: August 19, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SetStatusRWS(ResourceWorkflowStep)` \{#setstatusrws\} + +```text +Workflow step that can be used to change the status of a workflow with the click of a button. + +Options: + form_title(str): Title to be displayed at the top of the form. Defaults to the name of the step. + status_label(str): Custom label for the status select form field. Defaults to "Status". + statuses(list<dicts<status,label>>): List of dictionaries with two keys: "status" and "label". The value of "status" must be a valid status from the StatusMixin as determined by the valid_statuses() method on the step. The value of the "label" will be what is displayed to the user. If "label" is None or not given, the value of "status" will be displayed to the user. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#setstatusrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#setstatusrws-init-parameters\} + +```text +Initialize the parameters for this step. + +Returns: + dict<name:dict<help,value,required>>: Dictionary of all parameters with their initial value set. +``` + + +##### `validate_statuses(self)` \{#setstatusrws-validate-statuses\} + +```text +Validate the status dictionaries given in the "statuses" option. + +Raises: + RuntimeError: Invalid statuses or malformed status dictionaries. +``` diff --git a/website/docs/api/models/resource_workflow_steps/spatial_attributes_rws.mdx b/website/docs/api/models/resource_workflow_steps/spatial_attributes_rws.mdx new file mode 100644 index 00000000..b8125df6 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/spatial_attributes_rws.mdx @@ -0,0 +1,62 @@ +--- +id: models.resource_workflow_steps.spatial_attributes_rws +title: tethysext.atcore.models.resource_workflow_steps.spatial_attributes_rws +sidebar_label: spatial_attributes_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.spatial_attributes_rws` + +```text +******************************************************************************** +* Name: spatial_attributes_rws.py +* Author: nswain +* Created On: December 17, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `SpatialAttributesRWS(SpatialResourceWorkflowStep)` \{#spatialattributesrws\} + +```text +Workflow step used for setting simple valued attributes on features. + +Options: + geometry_source(varies): Geometry or parent to retrieve the geometry from. For passing geometry, use GeoJSON string. + attributes(dict): Dictionary of param instances defining the attributes to be defined for each feature. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#spatialattributesrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#spatialattributesrws-init-parameters\} + +```text +Initialize the parameters for this step. + +Args: + step_options(dict): Options for this step. + +Returns: + dict<name:dict<help,value>>: Dictionary of all parameters with their initial value set. +``` + + +##### `validate(self)` \{#spatialattributesrws-validate\} + +```text +Validates parameter values of this this step. + +Returns: + bool: True if data is valid, else Raise exception. + +Raises: + ValueError +``` diff --git a/website/docs/api/models/resource_workflow_steps/spatial_condor_job_rws.mdx b/website/docs/api/models/resource_workflow_steps/spatial_condor_job_rws.mdx new file mode 100644 index 00000000..87a40c48 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/spatial_condor_job_rws.mdx @@ -0,0 +1,60 @@ +--- +id: models.resource_workflow_steps.spatial_condor_job_rws +title: tethysext.atcore.models.resource_workflow_steps.spatial_condor_job_rws +sidebar_label: spatial_condor_job_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.spatial_condor_job_rws` + +```text +******************************************************************************** +* Name: spatial_condor_job_rws.py +* Author: nswain +* Created On: December 17, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `SpatialCondorJobRWS(SpatialResourceWorkflowStep)` \{#spatialcondorjobrws\} + +```text +Workflow step used for reviewing previous step parameters and submitting processing jobs to Condor. + +Options: + scheduler(str): Name of the Condor scheduler to use. + jobs(list<dict>): A list of dictionaries, each containing the kwargs for a CondorWorkflowJobNode. + workflow_kwargs(dict): Additional keyword arguments to pass to the CondorWorkflow. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#spatialcondorjobrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#spatialcondorjobrws-init-parameters\} + +```text +Initialize the parameters for this step. + +Returns: + dict<name:dict<help,value>>: Dictionary of all parameters with their initial value set. +``` + + +##### `validate(self)` \{#spatialcondorjobrws-validate\} + +```text +Validates parameter values of this this step. + +Returns: + bool: True if data is valid, else Raise exception. + +Raises: + ValueError +``` diff --git a/website/docs/api/models/resource_workflow_steps/spatial_dataset_rws.mdx b/website/docs/api/models/resource_workflow_steps/spatial_dataset_rws.mdx new file mode 100644 index 00000000..d4cb2bea --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/spatial_dataset_rws.mdx @@ -0,0 +1,90 @@ +--- +id: models.resource_workflow_steps.spatial_dataset_rws +title: tethysext.atcore.models.resource_workflow_steps.spatial_dataset_rws +sidebar_label: spatial_dataset_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.spatial_dataset_rws` + +```text +******************************************************************************** +* Name: spatial_dataset_rws.py +* Author: nswain +* Created On: March 5, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialDatasetRWS(SpatialResourceWorkflowStep)` \{#spatialdatasetrws\} + +```text +Workflow step used for setting dataset attributes on features. + +Options: + geometry_source(varies): Geometry or parent to retrieve the geometry from. For passing geometry, use GeoJSON string. + dataset_title(str): Title of the dataset (e.g.: Hydrograph). Defaults to 'Dataset'. + template_dataset(pd.DataFrame): A Pandas dataset to use as a template for the dataset. Default is pd.DataFrame(columns=['X', 'Y']) + read_only_columns(tuple,list): Names of columns of the template dataset that are read only. All columns are editable by default. + plot_columns(Union[2-tuple, list of 2-tuple]): Two columns to plot. First column given will be plotted on the x axis, the second on the y axis. No plot if not given. Multiple series plotted if a list of 2-tuple given, ex: [(x1, y1), (x2, y2)]. + max_rows(integer): Maximum number of rows allowed in the dataset. No maximum if not given. + empty_rows(integer): The number of empty rows to generate if an no/empty template dataset is given. + fixed_rows(bool): Indicates whether the number of rows in the table is fixed. + numeric_step(float): The step increment for numeric columns. + column_bounds(dict): A dictionary defining min and/or max bounds for numeric columns. For example: {'Column1': {'min': 0, 'max': 100}, 'Column2': {'min': -50}. Defaults to {}. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#spatialdatasetrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#spatialdatasetrws-init-parameters\} + +```text +Initialize the parameters for this step. + +Returns: + dict<name:dict<help,value,required>>: Dictionary of all parameters with their initial value set. +``` + + +##### `validate(self)` \{#spatialdatasetrws-validate\} + +```text +Validates parameter values of this this step. + +Returns: + bool: True if data is valid, else Raise exception. + +Raises: + ValueError +``` + + +##### `to_dict(self)` \{#spatialdatasetrws-to-dict\} + +```text +Serialize ResourceWorkflowStep into a dictionary. + +Returns: + dict: dictionary representation of ResourceWorkflowStep. +``` + + +##### `to_geojson(self, as_str=False)` \{#spatialdatasetrws-to-geojson\} + +```text +Serialize SpatialResourceWorkflowStep to GeoJSON. + +Args: + as_str(bool): Returns GeoJSON string if True, otherwise returns dict equivalent. + +Returns: + str or dict: GeoJSON string or dict equivalent representation of the spatial portions of a SpatialResourceWorkflowStep. +``` diff --git a/website/docs/api/models/resource_workflow_steps/spatial_input_rws.mdx b/website/docs/api/models/resource_workflow_steps/spatial_input_rws.mdx new file mode 100644 index 00000000..20f79a26 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/spatial_input_rws.mdx @@ -0,0 +1,82 @@ +--- +id: models.resource_workflow_steps.spatial_input_rws +title: tethysext.atcore.models.resource_workflow_steps.spatial_input_rws +sidebar_label: spatial_input_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.spatial_input_rws` + +```text +******************************************************************************** +* Name: spatial_input_rws.py +* Author: nswain +* Created On: December 17, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `SpatialInputRWS(SpatialResourceWorkflowStep)` \{#spatialinputrws\} + +```text +Workflow step used for retrieving simple spatial user input (points, lines, polygons). + +Options: + shapes(list): The types of shapes to allow. Any combination of 'points', 'lines', 'polygons', and/or 'extents'. + singular_name(str): Name to use when referring to a single feature in other areas of the user interface (e.g. "Detention Basin"). + plural_name(str): Name to use when referring to multiple features in other areas of the user interface (e.g. "Detention Basins"). + allow_shapefile(bool): Allow shapfile upload as spatial input. Defaults to True. + allow_drawing(bool): Allow manually drawing shapes. Defaults to True. + snapping_enabled(bool): Enabled snapping when drawing features. Defaults to True. + snapping_layer(dict): Specify a layer to snap to. Create a 1-dict where the key is the dot-path to the layer attribute to use in comparison and the value is the value to match (e.g. {'data.layer_id': 10}). + snapping_options(dict): Supported options include edge, vertex, pixelTolerance. See: https://openlayers.org/en/latest/apidoc/module-ol_interaction_Snap.html + allow_image(bool): Allow reference image upload as spatial input. Defaults to False. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#spatialinputrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#spatialinputrws-init-parameters\} + +```text +Initialize the parameters for this step. + +Returns: + dict<name:dict<help,value>>: Dictionary of all parameters with their initial value set. +``` + + +##### `validate(self)` \{#spatialinputrws-validate\} + +```text +Validates parameter values of this this step. + +Returns: + bool: True if data is valid, else Raise exception. + +Raises: + ValueError +``` + + +##### `validate_feature_attributes(self, attributes)` \{#spatialinputrws-validate-feature-attributes\} + +```text +Validate attribute values of a feature against the given param-based attributes definition. + +Args: + attributes(dict<attribute,value>): The attributes/properties of the feature to validate. + +Returns: + bool: True if data is valid, else Raise exception. + +Raises: + ValueError: If validation fails, a ValueError is raised with appropriate message to display to the user. +``` diff --git a/website/docs/api/models/resource_workflow_steps/spatial_rws.mdx b/website/docs/api/models/resource_workflow_steps/spatial_rws.mdx new file mode 100644 index 00000000..67e71745 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/spatial_rws.mdx @@ -0,0 +1,57 @@ +--- +id: models.resource_workflow_steps.spatial_rws +title: tethysext.atcore.models.resource_workflow_steps.spatial_rws +sidebar_label: spatial_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.spatial_rws` + +```text +******************************************************************************** +* Name: spatial_rws.py +* Author: nswain +* Created On: March 28, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `SpatialResourceWorkflowStep(ResourceWorkflowStep)` \{#spatialresourceworkflowstep\} + +```text +Abstract base class of all Spatial Resource Workflow Steps. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#spatialresourceworkflowstep-default-options\} + +> _No description._ + + + +##### `__init__(self, geoserver_name, map_manager, spatial_manager, *args, **kwargs)` \{#spatialresourceworkflowstep-init\} + +```text +Constructor. + +Args: + geoserver_name(str): Name of geoserver setting to use. + map_manager(MapManager): Instance of MapManager to use for the map view. + spatial_manager(SpatialManager): Instance of SpatialManager to use for the map view. +``` + + +##### `to_geojson(self, as_str=False)` \{#spatialresourceworkflowstep-to-geojson\} + +```text +Serialize SpatialResourceWorkflowStep to GeoJSON. + +Args: + as_str(bool): Returns GeoJSON string if True, otherwise returns dict equivalent. + +Returns: + str or dict: GeoJSON string or dict equivalent representation of the spatial portions of a SpatialResourceWorkflowStep. +``` diff --git a/website/docs/api/models/resource_workflow_steps/table_input_rws.mdx b/website/docs/api/models/resource_workflow_steps/table_input_rws.mdx new file mode 100644 index 00000000..1ada2dda --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/table_input_rws.mdx @@ -0,0 +1,66 @@ +--- +id: models.resource_workflow_steps.table_input_rws +title: tethysext.atcore.models.resource_workflow_steps.table_input_rws +sidebar_label: table_input_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.table_input_rws` + +```text +******************************************************************************** +* Name: table_input_rws.py +* Author: nswain +* Created On: March 5, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `TableInputRWS(ResourceWorkflowStep)` \{#tableinputrws\} + +```text +Workflow step used for setting table of data. + +Options: + dataset_title(str): Title of the dataset (e.g.: Hydrograph). Defaults to 'Dataset'. + template_dataset(pd.DataFrame): A Pandas dataset to use as a template for the dataset. Default is pd.DataFrame(columns=['X', 'Y']) + read_only_columns(tuple,list): Names of columns of the template dataset that are read only. All columns are editable by default. + plot_columns(Union[2-tuple, list of 2-tuple]): Two columns to plot. First column given will be plotted on the x axis, the second on the y axis. No plot if not given. Multiple series plotted if a list of 2-tuple given, ex: [(x1, y1), (x2, y2)]. + max_rows(integer): Maximum number of rows allowed in the dataset. No maximum if not given. + empty_rows(integer): The number of empty rows to generate if an no/empty template dataset is given. + fixed_rows(bool): Indicates whether the number of rows in the table is fixed. + numeric_step(float): The step increment for numeric columns. + column_bounds(dict): A dictionary defining min and/or max bounds for numeric columns. For example: {'Column1': {'min': 0, 'max': 100}, 'Column2': {'min': -50}. Defaults to {}. +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#tableinputrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#tableinputrws-init-parameters\} + +```text +Initialize the parameters for this step. + +Returns: + dict<name:dict<help,value,required>>: Dictionary of all parameters with their initial value set. +``` + + +##### `validate(self)` \{#tableinputrws-validate\} + +```text +Validates parameter values of this this step. + +Returns: + bool: True if data is valid, else Raise exception. + +Raises: + ValueError +``` diff --git a/website/docs/api/models/resource_workflow_steps/xms_tool_rws.mdx b/website/docs/api/models/resource_workflow_steps/xms_tool_rws.mdx new file mode 100644 index 00000000..fff2e600 --- /dev/null +++ b/website/docs/api/models/resource_workflow_steps/xms_tool_rws.mdx @@ -0,0 +1,93 @@ +--- +id: models.resource_workflow_steps.xms_tool_rws +title: tethysext.atcore.models.resource_workflow_steps.xms_tool_rws +sidebar_label: xms_tool_rws +--- + +# `tethysext.atcore.models.resource_workflow_steps.xms_tool_rws` + +```text +******************************************************************************** +* Name: xms_tool_rws.py +* Author: dgallup, ysun +* Created On: December, 2023 +* Copyright: (c) Aquaveo 2023 +******************************************************************************** +``` +## Classes + + +### `XMSToolRWS(ResourceWorkflowStep)` \{#xmstoolrws\} + +```text +Workflow step that can be used to get XMSTool input from a user. + +Example argument mapping (provide options from the database, for input arguments): +(Look in resource.datasets, where a dataset_type is a raster, for the dataset description, which is then filtered) +'arg_mapping': { + 'input_raster': { + 'resource_attr': 'datasets', + 'filter_attr': 'dataset_type', + 'valid_values': ['RASTER_ASCII', 'RASTER_GEOTIFF'], + 'name_attr': 'description', + 'name_attr_regex': r'"(.*?[^\])"', # optional regex expression on the name_attr value + }, +} + +Options: + form_title(str): Title to be displayed at the top of the form. Defaults to the name of the step. + status_label(str): Custom label for the status select form field. Defaults to "Status". + xmstool_class(dict): xms tool class used on the form. + arg_mapping(dict): dict of lookup options to map arguments to existing data. + renderer(str): Renderer option. Available values are 'django' and 'bokeh'. Defauls to 'django'. + validators (dict, optional): A dictionary of validator functions to check parameter values + before running the tool. Validators are called automatically when a parameter is set. + The structure can be: + + { + 'param_name': validator_func, # Single parameter + ('param_name_1', 'param_name_2'): validator_func # Multiple parameters + } + + Where `validator_func` is a callable that receives the parameter value(s) and raises a `ValueError` + if the value is invalid. + + Examples: + + # Validate a single parameter + def validate_start(value): + if value < 0: + raise ValueError("Start value must be non-negative") + + validators = { + 'start_time': validate_start + } + + # Validate multiple parameters together + def validate_range(start, end): + if start >= end: + raise ValueError("Start must be earlier than end") + + validators = { + ('start_time', 'end_time'): validate_range + } +``` +#### Methods + +*`@property`* + +##### `default_options(self)` \{#xmstoolrws-default-options\} + +> _No description._ + + + +##### `init_parameters(self, *args, **kwargs)` \{#xmstoolrws-init-parameters\} + +> _No description._ + + + +##### `validate(self)` \{#xmstoolrws-validate\} + +> _No description._ diff --git a/website/docs/api/models/types/_category_.json b/website/docs/api/models/types/_category_.json new file mode 100644 index 00000000..7ba79784 --- /dev/null +++ b/website/docs/api/models/types/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "types", + "position": 22 +} diff --git a/website/docs/api/models/types/guid.mdx b/website/docs/api/models/types/guid.mdx new file mode 100644 index 00000000..e315e27d --- /dev/null +++ b/website/docs/api/models/types/guid.mdx @@ -0,0 +1,39 @@ +--- +id: models.types.guid +title: tethysext.atcore.models.types.guid +sidebar_label: guid +--- + +# `tethysext.atcore.models.types.guid` + +> _No description._ + +## Classes + + +### `GUID(TypeDecorator)` \{#guid\} + +```text +Platform-independent GUID type. + +Uses Postgresql's UUID type, otherwise uses +CHAR(32), storing as stringified hex values. +``` +#### Methods + + +##### `load_dialect_impl(self, dialect)` \{#guid-load-dialect-impl\} + +> _No description._ + + + +##### `process_bind_param(self, value, dialect)` \{#guid-process-bind-param\} + +> _No description._ + + + +##### `process_result_value(self, value, dialect)` \{#guid-process-result-value\} + +> _No description._ diff --git a/website/docs/api/models/types/index.mdx b/website/docs/api/models/types/index.mdx new file mode 100644 index 00000000..a0be6c5c --- /dev/null +++ b/website/docs/api/models/types/index.mdx @@ -0,0 +1,14 @@ +--- +id: models.types.index +title: tethysext.atcore.models.types +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.models.types` + +> _No description._ + +## Modules + +- [`guid`](./guid.mdx) diff --git a/website/docs/api/permissions/_category_.json b/website/docs/api/permissions/_category_.json new file mode 100644 index 00000000..c6eff03b --- /dev/null +++ b/website/docs/api/permissions/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "permissions", + "position": 23 +} diff --git a/website/docs/api/permissions/app_users.mdx b/website/docs/api/permissions/app_users.mdx new file mode 100644 index 00000000..9a4795b8 --- /dev/null +++ b/website/docs/api/permissions/app_users.mdx @@ -0,0 +1,46 @@ +--- +id: permissions.app_users +title: tethysext.atcore.permissions.app_users +sidebar_label: app_users +--- + +# `tethysext.atcore.permissions.app_users` + +> _No description._ + +## Classes + + +### `PermissionsGenerator` \{#permissionsgenerator\} + +> _No description._ + +#### Methods + + +##### `__init__(self, permission_manager)` \{#permissionsgenerator-init\} + +```text +Used to generate permissions groups associated with the app_users extension. +Args: + permission_manager(AppPermissionManager): a permission manager instance bound to the app. +``` + + +##### `add_permissions_for(self, permission_group, permissions)` \{#permissionsgenerator-add-permissions-for\} + +```text +Add a list of permissions to the specified permission group. +Args: + permission_group(str): name of a permission group. + permissions(list<Permission>): list of Permission instances. +``` + + +##### `generate(self)` \{#permissionsgenerator-generate\} + +```text +Generate list of permission groups. +Returns: + list<PermissionGroups>: all permission groups with permissions. +``` diff --git a/website/docs/api/permissions/index.mdx b/website/docs/api/permissions/index.mdx new file mode 100644 index 00000000..4d0700af --- /dev/null +++ b/website/docs/api/permissions/index.mdx @@ -0,0 +1,20 @@ +--- +id: permissions.index +title: tethysext.atcore.permissions +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.permissions` + +```text +******************************************************************************** +* Name: __init__.py +* Author: nswain +* Created On: April 16, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Modules + +- [`app_users`](./app_users.mdx) diff --git a/website/docs/api/services/_category_.json b/website/docs/api/services/_category_.json new file mode 100644 index 00000000..6913f3cf --- /dev/null +++ b/website/docs/api/services/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "services", + "position": 24 +} diff --git a/website/docs/api/services/app_users/_category_.json b/website/docs/api/services/app_users/_category_.json new file mode 100644 index 00000000..3dc0c58e --- /dev/null +++ b/website/docs/api/services/app_users/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "app_users", + "position": 25 +} diff --git a/website/docs/api/services/app_users/decorators.mdx b/website/docs/api/services/app_users/decorators.mdx new file mode 100644 index 00000000..a81be87f --- /dev/null +++ b/website/docs/api/services/app_users/decorators.mdx @@ -0,0 +1,28 @@ +--- +id: services.app_users.decorators +title: tethysext.atcore.services.app_users.decorators +sidebar_label: decorators +--- + +# `tethysext.atcore.services.app_users.decorators` + +```text +******************************************************************************** +* Name: decorators +* Author: nswain +* Created On: April 09, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Functions + + +### `active_user_required()` \{#active-user-required\} + +> _No description._ + + + +### `resource_controller(is_rest_controller=False)` \{#resource-controller\} + +> _No description._ diff --git a/website/docs/api/services/app_users/func.mdx b/website/docs/api/services/app_users/func.mdx new file mode 100644 index 00000000..6fc5ba49 --- /dev/null +++ b/website/docs/api/services/app_users/func.mdx @@ -0,0 +1,30 @@ +--- +id: services.app_users.func +title: tethysext.atcore.services.app_users.func +sidebar_label: func +--- + +# `tethysext.atcore.services.app_users.func` + +```text +******************************************************************************** +* Name: func +* Author: nswain +* Created On: April 09, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Functions + + +### `get_display_name_for_django_user(django_user, default_to_username=True, append_username=False)` \{#get-display-name-for-django-user\} + +```text +Get a nice display name for a django user. +Args: + django_user: Django User object. + default_to_username: Return username if no other names are available if True, otherwise return the empty string. + append_username: Append the username in parenthesis if True. e.g.: "First Last (username)". + +Returns: In order of priority: "First Last", "First", "Last", "username". +``` diff --git a/website/docs/api/services/app_users/index.mdx b/website/docs/api/services/app_users/index.mdx new file mode 100644 index 00000000..33aa3899 --- /dev/null +++ b/website/docs/api/services/app_users/index.mdx @@ -0,0 +1,18 @@ +--- +id: services.app_users.index +title: tethysext.atcore.services.app_users +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.services.app_users` + +> _No description._ + +## Modules + +- [`decorators`](./decorators.mdx) +- [`func`](./func.mdx) +- [`licenses`](./licenses.mdx) +- [`permissions_manager`](./permissions_manager.mdx) +- [`roles`](./roles.mdx) diff --git a/website/docs/api/services/app_users/licenses.mdx b/website/docs/api/services/app_users/licenses.mdx new file mode 100644 index 00000000..734f04f8 --- /dev/null +++ b/website/docs/api/services/app_users/licenses.mdx @@ -0,0 +1,136 @@ +--- +id: services.app_users.licenses +title: tethysext.atcore.services.app_users.licenses +sidebar_label: licenses +--- + +# `tethysext.atcore.services.app_users.licenses` + +```text +******************************************************************************** +* Name: licenses +* Author: nswain +* Created On: April 04, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `Licenses` \{#licenses\} + +```text +Container and methods for licenses. +``` +#### Methods + + +##### `__contains__(self, item)` \{#licenses-contains\} + +> _No description._ + + + +##### `list(self)` \{#licenses-list\} + +```text +Get a list of all licenses. +Returns: + tuple: All available licenses. +``` + + +##### `is_valid(self, license)` \{#licenses-is-valid\} + +```text +Validate the given license. +Args: + license(str): valid license. +Returns: + bool: True if valid, else False. +``` + + +##### `get_rank_for(self, license)` \{#licenses-get-rank-for\} + +```text +Get the rank for the given license. +Args: + license(str): valid license. + +Returns: + int: rank value. +``` + + +##### `get_display_name_for(self, license)` \{#licenses-get-display-name-for\} + +```text +Get the display name for the given license. +Args: + license(str): valid license. + +Returns: + str: display name for license. +``` + + +##### `get_assign_permission_for(self, license)` \{#licenses-get-assign-permission-for\} + +```text +Get the name of the permission to check for assign rights of the given license. +Args: + license(str): valid license. + +Returns: + str: name of create permission for the given license. +``` + + +##### `compare(self, left_license, right_license)` \{#licenses-compare\} + +```text +Compare the rank of two licenses. +Args: + left_license(str): valid license. + right_license(str): valid license. + +Returns: + str: the winning license. +``` + + +##### `can_have_clients(self, license)` \{#licenses-can-have-clients\} + +```text +License based test to determine if an organization is allowed to have clients. +Args: + license: valid license. + +Returns: + bool: True if organization with this license can have clients, else False. +``` + + +##### `can_have_consultant(self, license)` \{#licenses-can-have-consultant\} + +```text +License based test to determine if an organization is allowed to be assigned a consultant. +Args: + license: valid license. + +Returns: + bool: True if organization with this license is allowed to be assigned a consultant, else False. +``` + + +##### `must_have_consultant(self, license)` \{#licenses-must-have-consultant\} + +```text +License based test to determine if an organization must be assigned a consultant. +Args: + license: valid license. + +Returns: + bool: True if organization with this license must be assigned a constultant, else False +``` diff --git a/website/docs/api/services/app_users/permissions_manager.mdx b/website/docs/api/services/app_users/permissions_manager.mdx new file mode 100644 index 00000000..4b13a4b4 --- /dev/null +++ b/website/docs/api/services/app_users/permissions_manager.mdx @@ -0,0 +1,161 @@ +--- +id: services.app_users.permissions_manager +title: tethysext.atcore.services.app_users.permissions_manager +sidebar_label: permissions_manager +--- + +# `tethysext.atcore.services.app_users.permissions_manager` + +```text +******************************************************************************** +* Name: permissions_manager.py +* Author: nswain +* Created On: April 09, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `AppPermissionsManager` \{#apppermissionsmanager\} + +> _No description._ + +#### Methods + + +##### `__init__(self, app_namespace)` \{#apppermissionsmanager-init\} + +```text +Manages custom_permissions for a given app. +Args: + app_namespace(str): Namespace of the app (e.g.: "my_first_app"). +``` + + +##### `list(self, with_namespace=False)` \{#apppermissionsmanager-list\} + +```text +List all permission groups. +Returns: + list<str>: names of all the custom_permissions groups. +``` + + +##### `get_permissions_group_for(self, role, license=None, **kwargs)` \{#apppermissionsmanager-get-permissions-group-for\} + +```text +Get the name of the custom_permissions group for the given role, license, and other criteria. +Args: + role(str): Role of user. + license(str): License of organization to which user belongs. + **kwargs: Used for additional criteria when extending functionality of this class. + +Returns: + str: name of permission group. +``` + + +##### `get_display_name_for(self, permissions_group)` \{#apppermissionsmanager-get-display-name-for\} + +```text +Get the display name for the given permission group. +Args: + permissions_group(str): name of permission group. + +Returns: + str: Display name for the given custom_permissions group. +``` + + +##### `get_has_role_permission_for(self, role, license=None)` \{#apppermissionsmanager-get-has-role-permission-for\} + +```text +Get name of the permission that can be tested to see if a user has the given role. +Args: + role(str): Role of user. + license(str): License of organization to which user belongs (optional). + +Returns: + str: name of the "has role" permission. +``` + + +##### `get_rank_for(self, permissions_group)` \{#apppermissionsmanager-get-rank-for\} + +```text +Get the rank for the given permission group. +Args: + permissions_group(str): name of permission group. + +Returns: + int: Rank for given permission group. +``` + +*`@staticmethod`* + +##### `add_permissions_group(app_user, permissions_group_name)` \{#apppermissionsmanager-add-permissions-group\} + +```text +Add the user to the role/group with the given name. +Args: + app_user(tethysext.atcore.models.AppUser): AppUser object + permissions_group_name(str): Name of group to add +``` + +*`@staticmethod`* + +##### `remove_permissions_group(app_user, permissions_group_name)` \{#apppermissionsmanager-remove-permissions-group\} + +```text +Remove the user from the role/group with the given name. +Args: + app_user(tethysext.atcore.models.AppUser): AppUser object + permissions_group_name(str): Name of group to remove +``` + + +##### `remove_all_permissions_groups(self, app_user)` \{#apppermissionsmanager-remove-all-permissions-groups\} + +```text +Remove the user from all permission groups of the bound app. +Args: + app_user(tethysext.atcore.models.AppUser): AppUser object +``` + + +##### `get_all_permissions_groups_for(self, app_user, as_display_name=False)` \{#apppermissionsmanager-get-all-permissions-groups-for\} + +```text +Get all of the custom_permissions groups to which the given user is assigned. +Args: + app_user(tethysext.atcore.models.AppUser): AppUser object + as_display_name(bool): Returns display names instead of programmatic name if True. + +Returns: + list: all custom_permissions group objects of the bound app to which the user belongs. +``` + + +##### `assign_user_permission(self, app_user, role, license=None, **kwargs)` \{#apppermissionsmanager-assign-user-permission\} + +```text +Add custom_permissions based on combo of role, license and other given criteria. + +Args: + app_user(tethysext.atcore.models.AppUser): AppUser object + role(str): Role of user. + license(str): License of organization to which user belongs. +``` + + +##### `remove_user_permission(self, app_user, role, license=None, **kwargs)` \{#apppermissionsmanager-remove-user-permission\} + +```text +Remove custom_permissions based on combo of role, license and other given criteria. + +Args: + app_user(tethysext.atcore.models.AppUser): AppUser object + role(str): Role of user. + license(str): License of organization to which user belongs. +``` diff --git a/website/docs/api/services/app_users/roles.mdx b/website/docs/api/services/app_users/roles.mdx new file mode 100644 index 00000000..1b155466 --- /dev/null +++ b/website/docs/api/services/app_users/roles.mdx @@ -0,0 +1,118 @@ +--- +id: services.app_users.roles +title: tethysext.atcore.services.app_users.roles +sidebar_label: roles +--- + +# `tethysext.atcore.services.app_users.roles` + +```text +******************************************************************************** +* Name: user_roles.py +* Author: nswain +* Created On: April 2, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `Roles` \{#roles\} + +```text +Container and methods for user roles. +``` +#### Methods + + +##### `__contains__(self, item)` \{#roles-contains\} + +> _No description._ + + + +##### `list(self)` \{#roles-list\} + +```text +Get a list of all roles. +Returns: + tuple: All available roles. +``` + + +##### `is_valid(self, role)` \{#roles-is-valid\} + +```text +Validate the given role. +Args: + role(str): valid role. +Returns: + bool: True if valid, else False. +``` + + +##### `get_rank_for(self, role)` \{#roles-get-rank-for\} + +```text +Get the rank for the given role. +Args: + role(str): valid role. + +Returns: + int: rank value. +``` + + +##### `get_display_name_for(self, role)` \{#roles-get-display-name-for\} + +```text +Get the display name for the given role. +Args: + role(str): valid role. + +Returns: + str: display name for role. +``` + + +##### `get_assign_permission_for(self, role)` \{#roles-get-assign-permission-for\} + +```text +Get the name of the permission to check for assign rights of the given role. +Args: + role(str): valid role. + +Returns: + str: name of create permission for the given role. +``` + + +##### `compare(self, left_role, right_role)` \{#roles-compare\} + +```text +Compare the rank of two roles. +Args: + left_role(str): valid role. + right_role(str): valid role. + +Returns: + str: the winning role. +``` + + +##### `get_organization_required_roles(self)` \{#roles-get-organization-required-roles\} + +```text +Users with these roles must be assigned to an organization. +Returns: + list: organization roles. +``` + + +##### `get_no_organization_roles(self)` \{#roles-get-no-organization-roles\} + +```text +Users with these roles cannot be assigned to an organization. +Returns: + list: organization roles. +``` diff --git a/website/docs/api/services/base_spatial_manager.mdx b/website/docs/api/services/base_spatial_manager.mdx new file mode 100644 index 00000000..8b2621b6 --- /dev/null +++ b/website/docs/api/services/base_spatial_manager.mdx @@ -0,0 +1,118 @@ +--- +id: services.base_spatial_manager +title: tethysext.atcore.services.base_spatial_manager +sidebar_label: base_spatial_manager +--- + +# `tethysext.atcore.services.base_spatial_manager` + +```text +******************************************************************************** +* Name: spatial_manager +* Author: nswain +* Created On: July 06, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `BaseSpatialManager(object)` \{#basespatialmanager\} + +```text +Base class for SpatialManagers. +``` +#### Methods + + +##### `__init__(self, geoserver_engine)` \{#basespatialmanager-init\} + +```text +Constructor + +Args: + workspace(str): The workspace to use when creating layers and styles. + geoserver_engine(tethys_dataset_services.GeoServerEngine): Tethys geoserver engine. +``` + +*`@abstractmethod`* + +##### `get_extent_for_project(self, *args, **kwargs)` \{#basespatialmanager-get-extent-for-project\} + +```text +Get extent of the project. +``` + +*`@abstractmethod`* + +##### `get_projection_units(self, *args, **kwargs)` \{#basespatialmanager-get-projection-units\} + +```text +Get units of the given projection. +``` + +*`@abstractmethod`* + +##### `get_projection_string(self, *args, **kwargs)` \{#basespatialmanager-get-projection-string\} + +```text +Get the projection string as either wkt or proj4 format. +``` + + +##### `create_workspace(self)` \{#basespatialmanager-create-workspace\} + +```text +Create workspace. +``` + + +##### `get_db_specific_store_id(self, model_db)` \{#basespatialmanager-get-db-specific-store-id\} + +```text +Construct the model database specific store id. + +Args: + model_db(ModelDatabase): the object representing the model database. +``` + + +##### `get_ows_endpoint(self, public_endpoint=True)` \{#basespatialmanager-get-ows-endpoint\} + +```text +Returns the GeoServer endpoint for OWS services (with trailing slash). + +Args: + public_endpoint(bool): return with the public endpoint if True. +``` + + +##### `get_wms_endpoint(self, public=True)` \{#basespatialmanager-get-wms-endpoint\} + +```text +Returns the GeoServer endpoint for WMS services (with trailing slash). + +Args: + public(bool): return with the public endpoint if True. +``` + + +##### `reload(self, ports=None, public_endpoint=True)` \{#basespatialmanager-reload\} + +```text +Reload the in memory catalog of each member of the geoserver cluster. +``` + + +## Functions + + +### `reload_config(public_endpoint=False, reload_config_default=True)` \{#reload-config\} + +```text +Decorator that handles config reload for methods of the SpatialManager class. + +Args: + public_endpoint(bool): Use public GeoServer endpoint for the reload call. + reload_config_default(bool): Default to use if the "reload_config" parameter is not specified. +``` diff --git a/website/docs/api/services/color_ramps.mdx b/website/docs/api/services/color_ramps.mdx new file mode 100644 index 00000000..600cb02a --- /dev/null +++ b/website/docs/api/services/color_ramps.mdx @@ -0,0 +1,11 @@ +--- +id: services.color_ramps +title: tethysext.atcore.services.color_ramps +sidebar_label: color_ramps +--- + +# `tethysext.atcore.services.color_ramps` + +> _No description._ + +_This module exposes no public classes or functions._ diff --git a/website/docs/api/services/exceptions.mdx b/website/docs/api/services/exceptions.mdx new file mode 100644 index 00000000..ab13763a --- /dev/null +++ b/website/docs/api/services/exceptions.mdx @@ -0,0 +1,28 @@ +--- +id: services.exceptions +title: tethysext.atcore.services.exceptions +sidebar_label: exceptions +--- + +# `tethysext.atcore.services.exceptions` + +```text +******************************************************************************** +* Name: exceptions +* Author: nswain +* Created On: July 10, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `UnitsNotFound(Exception)` \{#unitsnotfound\} + +> _No description._ + + + +### `UnknownUnits(Exception)` \{#unknownunits\} + +> _No description._ diff --git a/website/docs/api/services/file_database.mdx b/website/docs/api/services/file_database.mdx new file mode 100644 index 00000000..c03c5465 --- /dev/null +++ b/website/docs/api/services/file_database.mdx @@ -0,0 +1,326 @@ +--- +id: services.file_database +title: tethysext.atcore.services.file_database +sidebar_label: file_database +--- + +# `tethysext.atcore.services.file_database` + +```text +******************************************************************************** +* Name: file_database.py +* Author: glarsen +* Created On: November 10, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `FileDatabaseClient(MetaMixin)` \{#filedatabaseclient\} + +> _No description._ + +#### Methods + + +##### `__init__(self, session: Session, root_directory: str, file_database_id: uuid.UUID)` \{#filedatabaseclient-init\} + +```text +Use this constructor to bind the FileDatabaseClient to an existing FileDatabase. Use the new() factory method to create a new FileDatabase. + +Args: + session (sqlalchemy.orm.Session): The session for the SQL database. + root_directory (str): Path to the directory that contains the FileDatabase. + file_database_id (uuid.UUID): The id of the FileDatabase. +``` + +*`@classmethod`* + +##### `new(cls, session: Session, root_directory: str, meta: dict=None) -> 'FileDatabaseClient'` \{#filedatabaseclient-new\} + +```text +Use this method to create a new FileDatabase. Use the constructor to bind to an existing FileDatabase. + +Args: + session (sqlalchemy.orm.Session): The session for the SQL database. + root_directory (str or Path): Directory in which the new FileDatabase will be created. + meta (dict): The meta for the FileCollection. + +Returns: + FileDatabaseClient: A FileDatabaseClient bound to the new FileDatabase. +``` + +*`@property`* + +##### `instance(self) -> FileDatabase` \{#filedatabaseclient-instance\} + +```text +Property to get the underlying instance so it can be lazy loaded. +``` + +*`@property`* + +##### `root_directory(self) -> str` \{#filedatabaseclient-root-directory\} + +```text +The directory that contains the FileDatabase directory. +``` + +*`@property`* + +##### `path(self) -> str` \{#filedatabaseclient-path\} + +```text +Path to the FileDatabase directory. +``` + + +##### `delete(self)` \{#filedatabaseclient-delete\} + +```text +Delete this FileDatabase +``` + + +##### `get_collection(self, collection_id: uuid.UUID) -> 'FileCollectionClient'` \{#filedatabaseclient-get-collection\} + +```text +Get a FileCollectionClient owned by this FileDatabase by its collection_id + +Args: + collection_id (uuid.UUID): The id for the file collection owned by this FileDatabase. + +Returns: + The FileCollectionClient for the FileCollection. +``` + + +##### `new_collection(self, items: list=None, meta: dict=None, move: bool=False, relative_to: str='') -> 'FileCollectionClient'` \{#filedatabaseclient-new-collection\} + +```text +Create a new collection copying any files and meta data passed in. + +Args: + items (list): A list of files or paths to be copied to the file collection. + meta: (dict): The meta data to be stored in the FileCollection. + move (bool): Move the files if True, otherwise copy the files. Defaults to False. + relative_to (str): Preserve the relative path of the items relative to this directory. + +Returns: + A new FileCollectionClient object for the FileCollection. +``` + + +##### `delete_collection(self, collection_id: uuid.UUID) -> None` \{#filedatabaseclient-delete-collection\} + +```text +Delete a specified FileCollection from the FileDatabase + +Args: + collection_id (uuid.UUID): The id for the collection to be deleted. +``` + + +##### `export_collection(self, collection_id: uuid.UUID, target: str) -> None` \{#filedatabaseclient-export-collection\} + +```text +Export the collection to a target location. + +Args: + collection_id (uuid.UUID): The id for the file collection to be exported. + target (str): Path to the target location. +``` + + +##### `duplicate_collection(self, collection_id: uuid.UUID) -> 'FileCollectionClient'` \{#filedatabaseclient-duplicate-collection\} + +```text +Duplicate a collection and add it to the FileDatabase + +Args: + collection_id (uuid.UUID): The id for the collection to be duplicated. + +Returns: + A FileCollectionClient for the newly duplicated FileCollect +``` + + + +### `FileCollectionClient(MetaMixin)` \{#filecollectionclient\} + +> _No description._ + +#### Methods + + +##### `__init__(self, session: Session, file_database_client: FileDatabaseClient, file_collection_id: uuid.UUID)` \{#filecollectionclient-init\} + +```text +Use this constructor to bind the FileCollectionClient to an existing FileCollection. Use the new() factory method to create a new FileCollection. + +Args: + session (sqlalchemy.orm.Session): The session for the SQL database. + file_database_client (FileDatabaseClient): A FileDatabaseClient bound to the FileDatabase containing the FileCollection. + file_collection_id (uuid.UUID): The id of the FileCollection. +``` + +*`@classmethod`* + +##### `new(cls, session: Session, file_database_client: FileDatabaseClient, meta: dict=None) -> 'FileCollectionClient'` \{#filecollectionclient-new\} + +```text +Use this method to create a new FileCollection. Use the constructor to bind to an existing FileCollection. + +Args: + session (sqlalchemy.orm.Session): The session for the SQL database. + file_database_client (FileDatabaseClient): A FileDatabaseClient bound to the FileDatabase in which you would like the new FileCollection to be created. + meta (dict): The meta for the FileCollection + +Returns: + A client to a newly generated FileCollection. +``` + +*`@property`* + +##### `instance(self) -> FileCollection` \{#filecollectionclient-instance\} + +```text +Property to get the underlying instance so it can be lazy loaded. + +Returns: + The underlying FileCollection instance. + +Raises: + Exception: If the the instance has been deleted. +``` + +*`@property`* + +##### `file_database_client(self) -> FileDatabaseClient` \{#filecollectionclient-file-database-client\} + +```text +FileDatabaseClient bound to the FileDatabase containing the FileCollection. +``` + +*`@property`* + +##### `path(self) -> str` \{#filecollectionclient-path\} + +```text +Path to the FileCollection directory. +``` + +*`@property`* + +##### `files(self) -> Generator[str, None, None]` \{#filecollectionclient-files\} + +```text +Generator that iterates recursively through all files (ignoring empty directories). + +Returns: + Generate giving a list of files in the FileCollection +``` + + +##### `delete(self)` \{#filecollectionclient-delete\} + +```text +Delete this CollectionInstance +``` + + +##### `export(self, target)` \{#filecollectionclient-export\} + +```text +Copy the FileCollection to the target. + +Args: + target (str): location of the newly exported FileCollection. +``` + + +##### `duplicate(self)` \{#filecollectionclient-duplicate\} + +```text +Duplicate collection with a new ID and associate it with the current FileDatabase + +Returns: + A client for the newly duplicated FileCollection +``` + + +##### `has_item(self, item: str)` \{#filecollectionclient-has-item\} + +```text +Check if an item is in the file collection. + +Args: + item: The path to the item to be checked. +``` + + +##### `add_item(self, item: str, move: bool=False, relative_to: str='') -> None` \{#filecollectionclient-add-item\} + +```text +Add an item to the file collection. + +Args: + item: A path to the item to be added. + move: Move the file if True, otherwise just copy. + relative_to: Preserve the relative path of the item relative to this directory. +``` + + +##### `delete_item(self, item: str)` \{#filecollectionclient-delete-item\} + +```text +Delete an item from the file collection. + +Args: + item (str): Path to the item to be deleted, relative to the collection. +``` + + +##### `export_item(self, item: str, target: str)` \{#filecollectionclient-export-item\} + +```text +Export an item from the collection to a new location. + +Args: + item (str): Path to the item to be exported, relative to the collection. + target (str): Path to the export location. +``` + + +##### `duplicate_item(self, item, new_item)` \{#filecollectionclient-duplicate-item\} + +```text +Duplicate an item in the collection. + +Args: + item (str): Path to the item to duplicate, relative to the collection. + new_item (str): Path to the new item, relative to the collection. +``` + +*`@contextmanager`* + +##### `open_file(self, file, *args, **kwargs)` \{#filecollectionclient-open-file\} + +```text +Open a file in the collection for reading/writing. + +Args: + file: The file to be opened, relative to the collection. + args, kwargs: Additional arguments passed to open function. + +Returns: + A handle to the file that has been opened. +``` + + +##### `walk(self)` \{#filecollectionclient-walk\} + +```text +Walk through the files, and directories of the collection recursively. +``` diff --git a/website/docs/api/services/index.mdx b/website/docs/api/services/index.mdx new file mode 100644 index 00000000..bcb3213c --- /dev/null +++ b/website/docs/api/services/index.mdx @@ -0,0 +1,33 @@ +--- +id: services.index +title: tethysext.atcore.services +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.services` + +> _No description._ + +## Modules + +- [`app_users`](./app_users/index.mdx) +- [`base_spatial_manager`](./base_spatial_manager.mdx) +- [`color_ramps`](./color_ramps.mdx) +- [`exceptions`](./exceptions.mdx) +- [`file_database`](./file_database.mdx) +- [`map_manager`](./map_manager.mdx) +- [`model_database`](./model_database.mdx) +- [`model_database_base`](./model_database_base.mdx) +- [`model_database_connection`](./model_database_connection.mdx) +- [`model_database_connection_base`](./model_database_connection_base.mdx) +- [`model_db_spatial_manager`](./model_db_spatial_manager.mdx) +- [`model_file_database`](./model_file_database.mdx) +- [`model_file_database_connection`](./model_file_database_connection.mdx) +- [`model_file_db_spatial_manager`](./model_file_db_spatial_manager.mdx) +- [`paginate`](./paginate.mdx) +- [`resource_condor_workflow`](./resource_condor_workflow.mdx) +- [`resource_spatial_manager`](./resource_spatial_manager.mdx) +- [`resource_workflows`](./resource_workflows/index.mdx) +- [`spatial_reference`](./spatial_reference.mdx) +- [`workflow_manager`](./workflow_manager/index.mdx) diff --git a/website/docs/api/services/map_manager.mdx b/website/docs/api/services/map_manager.mdx new file mode 100644 index 00000000..0d0ccafd --- /dev/null +++ b/website/docs/api/services/map_manager.mdx @@ -0,0 +1,311 @@ +--- +id: services.map_manager +title: tethysext.atcore.services.map_manager +sidebar_label: map_manager +--- + +# `tethysext.atcore.services.map_manager` + +```text +******************************************************************************** +* Name: map_manager +* Author: nswain +* Created On: August 30, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `MapManagerBase(object)` \{#mapmanagerbase\} + +```text +Base class for object that orchestrates the map layers and resources. +``` +#### Methods + + +##### `__init__(self, spatial_manager, resource)` \{#mapmanagerbase-init\} + +> _No description._ + + +*`@property`* + +##### `map_extent(self)` \{#mapmanagerbase-map-extent\} + +> _No description._ + + +*`@property`* + +##### `default_view(self)` \{#mapmanagerbase-default-view\} + +> _No description._ + + +*`@abstractmethod`* + +##### `compose_map(self, request, *args, **kwargs)` \{#mapmanagerbase-compose-map\} + +```text +Compose the MapView object, its default extent, and any layer groups. + +The MapView controller calls this method and unpacks the result as +``map_view, model_extent, layer_groups = map_manager.compose_map(...)``, +so subclass implementations must return all three values. + +Args: + request(HttpRequest): A Django request object. + +Returns: + tuple: A 3-tuple of ``(MapView, 4-list<float>, list<dict>)``: + + - **MapView** — the configured Tethys ``MapView`` gizmo. + - **4-list<float>** — the default map extent ``[minx, miny, maxx, maxy]``. + - **list<dict>** — layer groups built via :meth:`build_layer_group`. + May be empty. + +Notes: + The ``MapView`` controller overwrites ``controls``, ``legend``, + ``height``, ``width``, ``feature_selection``, and ``disable_basemap`` + on the returned ``MapView`` after this method runs, so setting + those fields here has no effect. +``` + + +##### `get_cesium_token(self)` \{#mapmanagerbase-get-cesium-token\} + +```text +Get the cesium token for Cesium Views + +Returns: + str: The cesium API token +``` + + +##### `build_param_string(self, **kwargs)` \{#mapmanagerbase-build-param-string\} + +```text +Build a VIEWPARAMS or ENV string with given kwargs (e.g.: 'foo:1;bar:baz') + +Args: + **kwargs: key-value pairs of paramaters. + +Returns: + str: parameter string. +``` + + +##### `build_geojson_layer(self, geojson, layer_name, layer_title, layer_variable, layer_id='', visible=True, public=True, selectable=False, plottable=False, has_action=False, extent=None, popup_title=None, excluded_properties=None, show_download=False, label_options=None)` \{#mapmanagerbase-build-geojson-layer\} + +```text +Build an MVLayer object with supplied arguments. +Args: + geojson(dict): Python equivalent GeoJSON FeatureCollection. + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + show_download(boolean): enable download geojson as shapefile. Default is False. + label_options(dict): Dictionary for labeling. Possibilities include label_property (the name of the + property to label), font (label font), text_align (alignment of the label), offset_x (x offset). Optional. + +Returns: + MVLayer: the MVLayer object. +``` + + +##### `build_cesium_layer(self, cesium_type, cesium_json, layer_name, layer_title, layer_variable, layer_id='', visible=True, public=True, selectable=False, plottable=False, has_action=False, extent=None, popup_title=None, excluded_properties=None, show_download=False)` \{#mapmanagerbase-build-cesium-layer\} + +```text +Build an MVLayer object with supplied arguments. +Args: + cesium_type(enum): 'CesiumModel' or 'CesiumPrimitive'. + cesium_json(dict): Cesium dictionary to describe the layer. + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + show_download(boolean): enable download geojson as shapefile. Default is False. + +Returns: + MVLayer: the MVLayer object. +``` + + +##### `build_wms_layer(self, endpoint, layer_name, layer_title, layer_variable, style='', viewparams=None, env=None, visible=True, tiled=True, selectable=False, plottable=False, has_action=False, extent=None, public=True, geometry_attribute='geometry', layer_id='', excluded_properties=None, popup_title=None, use_geoserver_legend=True, geoserver_legend_params=None, color_ramp_division_kwargs=None, times=None)` \{#mapmanagerbase-build-wms-layer\} + +```text +Build an WMS MVLayer object with supplied arguments. +Args: + endpoint(str): URL to GeoServer WMS interface. + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + style(str): Name of the Geoserver layer style + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + viewparams(str): VIEWPARAMS string. + env(str): ENV string. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + tiled(bool): Configure as tiled layer if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + geometry_attribute(str): Name of the geometry attribute. Defaults to "geometry". + use_geoserver_legend(bool): If True, the legend will be retrieved directly from Geoserver. If False, + a legend will be generated locally using the parameter `color_ramp_division_kwargs`. + geoserver_legend_params: Dictionary of additional GeoServer GetLegendGraphic request parameters. + Both standard WMS parameters (e.g. "transparent", "format") and legend-specific options ("legend_options") can be included. + Example: + { + "transparent": "true", + "format": "image/png", + "legend_options": "hideEmptyRules:true;fontSize:12" + } + color_ramp_division_kwargs(dict): arguments from map_manager.generate_custom_color_ramp_divisions + times (list): List of time steps if layer is time-enabled. Times should be represented as strings in ISO 8601 format (e.g.: ["20210322T112511Z", "20210322T122511Z", "20210322T132511Z"]). Currently only supported in CesiumMapView. +Returns: + MVLayer: the MVLayer object. +``` + + +##### `build_arc_gis_layer(self, endpoint, layer_name, layer_title, layer_variable, viewparams=None, env=None, visible=True, tiled=True, selectable=False, plottable=False, has_action=False, extent=None, public=True, geometry_attribute='geometry', layer_id='', excluded_properties=None, popup_title=None)` \{#mapmanagerbase-build-arc-gis-layer\} + +```text +Build an AcrGIS Map Server MVLayer object with supplied arguments. +Args: + endpoint(str): URL to GeoServer WMS interface. + layer_name(str): Name of GeoServer layer (e.g.: my_workspace:3a84ff62-aaaa-bbbb-cccc-1a2b3c4d5a6b7c8d-model_boundaries). + layer_title(str): Title of MVLayer (e.g.: Model Boundaries). + layer_variable(str): Variable type of the layer (e.g.: model_boundaries). + layer_id(UUID, int, str): layer_id for non geoserver layer where layer_name may not be unique. + viewparams(str): VIEWPARAMS string. + env(str): ENV string. + visible(bool): Layer is visible when True. Defaults to True. + public(bool): Layer is publicly accessible when app is running in Open Portal Mode if True. Defaults to True. + tiled(bool): Configure as tiled layer if True. Defaults to True. + selectable(bool): Enable feature selection. Defaults to False. + plottable(bool): Enable "Plot" button on pop-up properties. Defaults to False. + has_action(bool): Enable "Action" button on pop-up properties. Defaults to False. + extent(list): Extent for the layer. Optional. + popup_title(str): Title to display on feature popups. Defaults to layer title. + excluded_properties(list): List of properties to exclude from feature popups. + geometry_attribute(str): Name of the geometry attribute. Defaults to "geometry". + +Returns: + MVLayer: the MVLayer object. +``` + + +##### `build_layer_group(self, id, display_name, layers, layer_control='checkbox', visible=True, public=True)` \{#mapmanagerbase-build-layer-group\} + +```text +Build a layer group object. + +Args: + id(str): Unique identifier for the layer group. + display_name(str): Name displayed in MapView layer selector/legend. + layers(list<MVLayer>): List of layers to include in the layer group. + layer_control(str): Type of control for layers. Either 'checkbox' or 'radio'. Defaults to checkbox. + visible(bool): Whether layer group is initially visible. Defaults to True. + public(bool): enable public to see this layer group if True. +Returns: + dict: Layer group definition. +``` + + +##### `get_vector_style_map(self)` \{#mapmanagerbase-get-vector-style-map\} + +```text +Builds the style map for vector layers. + +Returns: + dict: the style map. +``` + + +##### `get_wms_endpoint(self)` \{#mapmanagerbase-get-wms-endpoint\} + +```text +Get the public wms endpoint for GeoServer. +``` + + +##### `get_map_extent(self)` \{#mapmanagerbase-get-map-extent\} + +```text +Get the default view and extent for the project. + +Returns: + MVView, 4-list<float>: default view and extent of the project. +``` + + +##### `build_legend(self, layer, units='')` \{#mapmanagerbase-build-legend\} + +```text +Build Legend data for a given layer + +Args: + layer: result.layer object + units: unit for the legend. +Returns: + Legend data associate with the layer. +``` + + +##### `generate_custom_color_ramp_divisions(self, min_value, max_value, num_divisions=10, value_precision=2, first_division=1, top_offset=0, bottom_offset=0, prefix='val', color_ramp='', color_prefix='color', no_data_value=None)` \{#mapmanagerbase-generate-custom-color-ramp-divisions\} + +```text +Generate custom elevation divisions. + +Args: + min_value(number): minimum value. + max_value(number): maximum value. + num_divisison(int): number of divisions. + value_precision(int): level of precision for legend values. + first_division(int): first division number (defaults to 1). + top_offset(number): offset from top of color ramp (defaults to 0). + bottom_offset(number): offset from bottom of color ramp (defaults to 0). + prefix(str): name of division variable prefix (i.e.: 'val' for pattern 'val1'). + color_ramp(str): color ramp name in COLOR_RAMPS dict. Options are ['Blue', 'Blue and Red', 'Flower Field', 'Galaxy Berries', 'Heat Map', 'Olive Harmony', 'Mother Earth', 'Rainforest Frogs', 'Retro FLow', 'Sunset Fade'] + color_prefix(str): name of color variable prefix (i.e.: 'color' for pattern 'color1'). + no_data_value (str): set no data value for the color ramp. (defaults to None). +Returns: + dict<name, value>: custom divisions +``` + + +##### `get_plot_for_layer_feature(self, layer_name, feature_id)` \{#mapmanagerbase-get-plot-for-layer-feature\} + +```text +Get plot data for given feature on given layer. + +Args: + layer_name(str): Name/id of layer. + feature_id(str): PostGIS Feature ID of feature. + +Returns: + str, list<dict>, dict: plot title, data series, and layout options, respectively. +``` diff --git a/website/docs/api/services/model_database.mdx b/website/docs/api/services/model_database.mdx new file mode 100644 index 00000000..d4c7ed6b --- /dev/null +++ b/website/docs/api/services/model_database.mdx @@ -0,0 +1,126 @@ +--- +id: services.model_database +title: tethysext.atcore.services.model_database +sidebar_label: model_database +--- + +# `tethysext.atcore.services.model_database` + +```text +******************************************************************************** +* Name: model_database.py +* Author: nswain +* Created On: June 5, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelDatabase(ModelDatabaseBase)` \{#modeldatabase\} + +```text +Manages the creation of databases for models and will load-balance between multiple database connections if defined by the app. # noqa: E501 +``` +#### Methods + + +##### `get_name(self)` \{#modeldatabase-get-name\} + +```text +DB name getter (e.g.: my_app_02893760_1f1e_43a2_8578_b10fc829c15f). +``` + + +##### `get_id(self)` \{#modeldatabase-get-id\} + +```text +DB id getter (e.g.: 02893760_1f1e_43a2_8578_b10fc829c15f). +``` + + +##### `get_size(self, pretty=False)` \{#modeldatabase-get-size\} + +```text +Get the size of the ModelDatabase +``` + +*`@property`* + +##### `db_url(self)` \{#modeldatabase-db-url\} + +> _No description._ + + +*`@property`* + +##### `db_url_obj(self)` \{#modeldatabase-db-url-obj\} + +> _No description._ + + +*`@property`* + +##### `model_db_connection(self)` \{#modeldatabase-model-db-connection\} + +> _No description._ + + + +##### `exists(self)` \{#modeldatabase-exists\} + +```text +Returns true if the model database exists. +``` + + +##### `list(self)` \{#modeldatabase-list\} + +```text +Returns a list names of all the model databases. +``` + + +##### `get_engine(self)` \{#modeldatabase-get-engine\} + +```text +Returns an SQLAlchemy engine for the model database. +``` + + +##### `get_session(self)` \{#modeldatabase-get-session\} + +```text +Returns an SQLAlchemy session for the model database. +``` + + +##### `get_session_maker(self)` \{#modeldatabase-get-session-maker\} + +```text +Returns an SQLAlchemy session maker for the model database. +``` + + +##### `initialize(self, declarative_bases=(), spatial=False)` \{#modeldatabase-initialize\} + +```text +Creates a new model database if it doesn't exist and initializes it with the data models passed in (if any). + +Args: + declarative_bases(tuple): one or more SQLAlchemy declarative base classes used to initialize tables. + spatial(bool): enable postgis extension on model database if True. + +Returns: + database_id of the model database +``` + + +##### `delete(self)` \{#modeldatabase-delete\} + +```text +Delete the database associated with this model database. + +Returns: + bool: True if successfully deleted, otherwise False. +``` diff --git a/website/docs/api/services/model_database_base.mdx b/website/docs/api/services/model_database_base.mdx new file mode 100644 index 00000000..a640e589 --- /dev/null +++ b/website/docs/api/services/model_database_base.mdx @@ -0,0 +1,99 @@ +--- +id: services.model_database_base +title: tethysext.atcore.services.model_database_base +sidebar_label: model_database_base +--- + +# `tethysext.atcore.services.model_database_base` + +```text +******************************************************************************** +* Name: model_database_base.py +* Author: nswain & ckrewson +* Created On: December 5, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelDatabaseBase(object)` \{#modeldatabasebase\} + +> _No description._ + +#### Methods + + +##### `__init__(self, app, database_id=None)` \{#modeldatabasebase-init\} + +```text +Constructor + +Args: + app(TethysApp): TethysApp class or instance. + database_id(str): UUID to be assigned to the database. +``` + +*`@abc.abstractmethod`* + +##### `get_name(self)` \{#modeldatabasebase-get-name\} + +> _No description._ + + +*`@abc.abstractmethod`* + +##### `get_id(self)` \{#modeldatabasebase-get-id\} + +> _No description._ + + +*`@property` `@abc.abstractmethod`* + +##### `model_db_connection(self)` \{#modeldatabasebase-model-db-connection\} + +> _No description._ + + +*`@abc.abstractmethod`* + +##### `initialize(self, *args, **kwargs)` \{#modeldatabasebase-initialize\} + +> _No description._ + + + +##### `pre_initialize(self, *args, **kwargs)` \{#modeldatabasebase-pre-initialize\} + +```text +Override to perform additional initialize steps before the database and tables have been initialized. +``` + + +##### `post_initialize(self, *args, **kwargs)` \{#modeldatabasebase-post-initialize\} + +```text +Override to perform additional initialize steps after the database and tables have been initialized. +``` + +*`@abc.abstractmethod`* + +##### `exists(self)` \{#modeldatabasebase-exists\} + +> _No description._ + + +*`@abc.abstractmethod`* + +##### `list(self)` \{#modeldatabasebase-list\} + +> _No description._ + + +*`@classmethod`* + +##### `generate_id(cls)` \{#modeldatabasebase-generate-id\} + +```text +Returns a UUID name for databases. +``` diff --git a/website/docs/api/services/model_database_connection.mdx b/website/docs/api/services/model_database_connection.mdx new file mode 100644 index 00000000..e5fb22ab --- /dev/null +++ b/website/docs/api/services/model_database_connection.mdx @@ -0,0 +1,58 @@ +--- +id: services.model_database_connection +title: tethysext.atcore.services.model_database_connection +sidebar_label: model_database_connection +--- + +# `tethysext.atcore.services.model_database_connection` + +```text +******************************************************************************** +* Name: model_database_connection.py +* Author: nswain +* Created On: June 05, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelDatabaseConnection(ModelDatabaseConnectionBase)` \{#modeldatabaseconnection\} + +```text +Represents a Model Database. +``` +#### Methods + + +##### `__init__(self, db_url, db_app_namespace=None, db_engine_kwargs=None)` \{#modeldatabaseconnection-init\} + +```text +Constructor + +Args: + db_url(str): SQLAlchemy url connection string. + db_app_namespace(str): namespace prepended by persistent store API if applicable. + db_engine_kwargs(dict): Optional arguments to pass to SQLAlchemy create_engine method. +``` + + +##### `get_engine(self)` \{#modeldatabaseconnection-get-engine\} + +```text +Returns an SQLAlchemy engine for the model database. +``` + + +##### `get_session_maker(self)` \{#modeldatabaseconnection-get-session-maker\} + +```text +Returns an SQLAlchemy session maker for the model database. +``` + + +##### `get_session(self)` \{#modeldatabaseconnection-get-session\} + +```text +Returns an SQLAlchemy session for the model database. +``` diff --git a/website/docs/api/services/model_database_connection_base.mdx b/website/docs/api/services/model_database_connection_base.mdx new file mode 100644 index 00000000..fc177190 --- /dev/null +++ b/website/docs/api/services/model_database_connection_base.mdx @@ -0,0 +1,39 @@ +--- +id: services.model_database_connection_base +title: tethysext.atcore.services.model_database_connection_base +sidebar_label: model_database_connection_base +--- + +# `tethysext.atcore.services.model_database_connection_base` + +```text +******************************************************************************** +* Name: model_file_database_connection_base.py +* Author: nswain +* Created On: June 05, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelDatabaseConnectionBase(object)` \{#modeldatabaseconnectionbase\} + +```text +Represents a Model Database. +``` +#### Methods + + +##### `get_id(self)` \{#modeldatabaseconnectionbase-get-id\} + +```text +DB id getter. +``` + + +##### `get_name(self)` \{#modeldatabaseconnectionbase-get-name\} + +```text +DB name getter. +``` diff --git a/website/docs/api/services/model_db_spatial_manager.mdx b/website/docs/api/services/model_db_spatial_manager.mdx new file mode 100644 index 00000000..54661170 --- /dev/null +++ b/website/docs/api/services/model_db_spatial_manager.mdx @@ -0,0 +1,90 @@ +--- +id: services.model_db_spatial_manager +title: tethysext.atcore.services.model_db_spatial_manager +sidebar_label: model_db_spatial_manager +--- + +# `tethysext.atcore.services.model_db_spatial_manager` + +```text +******************************************************************************** +* Name: model_db_spatial_manager +* Author: nswain +* Created On: July 06, 2018 +* Updated on: December 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelDBSpatialManager(BaseSpatialManager)` \{#modeldbspatialmanager\} + +```text +Class for Spatial Managers using a postgres database. +``` +#### Methods + +*`@abstractmethod`* + +##### `get_extent_for_project(self, model_db)` \{#modeldbspatialmanager-get-extent-for-project\} + +```text +Return the extent / bounding box for a project/model. +Args: + model_db (ModelDatabase): the object representing the model database. +Returns: + 4-list: Extent bounding box (e.g.: [minx, miny, maxx, maxy] ). +``` + + +##### `get_projection_units(self, model_db, srid)` \{#modeldbspatialmanager-get-projection-units\} + +```text +Get units of the given projection. + +Args: + model_db(ModelDatabase): the object representing the model database.: + srid(int): EPSG spatial reference identifier. + +Returns: + str: SpatialManager.U_METRIC or SpatialManager.U_IMPERIAL +``` + + +##### `get_projection_string(self, model_db, srid, proj_format='')` \{#modeldbspatialmanager-get-projection-string\} + +```text +Get the projection string as either wkt or proj4 format. + +Args: + model_db(ModelDatabase): the object representing the model database.: + srid(int): EPSG spatial reference identifier. + proj_format(str): project string format (either SpatialManager.PRO_WKT or SpatialManager.PRO_PROJ4). + +Returns: + str: projection string. +``` + + +##### `link_geoserver_to_db(self, model_db, reload_config=True)` \{#modeldbspatialmanager-link-geoserver-to-db\} + +```text +Link GeoServer to a Model Database. + +Args: + model_db(ModelDatabase): the object representing the model database. + reload_config(bool): Reload the geoserver node configuration and catalog before returning if True. +``` + + +##### `unlink_geoserver_from_db(self, model_db, purge=False, recurse=False)` \{#modeldbspatialmanager-unlink-geoserver-from-db\} + +```text +Unlink GeoServer from a Model Database. + +Args: + model_db(ModelDatabase): the object representing the model database. + purge(bool): delete configuration files from filesystem if True. + recurse(bool): recursively delete any dependent objects if True. +``` diff --git a/website/docs/api/services/model_file_database.mdx b/website/docs/api/services/model_file_database.mdx new file mode 100644 index 00000000..cf52d61c --- /dev/null +++ b/website/docs/api/services/model_file_database.mdx @@ -0,0 +1,112 @@ +--- +id: services.model_file_database +title: tethysext.atcore.services.model_file_database +sidebar_label: model_file_database +--- + +# `tethysext.atcore.services.model_file_database` + +```text +******************************************************************************** +* Name: model_file_database.py +* Author: nswain & ckrewson +* Created On: December 5, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelFileDatabase(ModelDatabaseBase)` \{#modelfiledatabase\} + +```text +Manages the creation of file databases for models. # noqa: E501 +``` +#### Methods + +*`@property`* + +##### `database_root(self)` \{#modelfiledatabase-database-root\} + +```text +Returns path of root path of resource directory +``` + +*`@property`* + +##### `directory(self)` \{#modelfiledatabase-directory\} + +```text +Returns path of resource directory +``` + +*`@property`* + +##### `model_db_connection(self)` \{#modelfiledatabase-model-db-connection\} + +> _No description._ + + + +##### `get_name(self)` \{#modelfiledatabase-get-name\} + +```text +DB name getter (e.g.: my_app_02893760_1f1e_43a2_8578_b10fc829c15f). +``` + + +##### `get_id(self)` \{#modelfiledatabase-get-id\} + +```text +DB id getter (e.g.: 02893760_1f1e_43a2_8578_b10fc829c15f). +``` + + +##### `duplicate(self)` \{#modelfiledatabase-duplicate\} + +```text +makes a copy of resource directory with new uuid + +Returns: + Instance of the duplicated model file database +``` + + +##### `exists(self)` \{#modelfiledatabase-exists\} + +```text +Check if the model file database exists. + +Returns: + True if the model file database exists +``` + + +##### `list_databases(self)` \{#modelfiledatabase-list-databases\} + +```text +Returns: + List of all models in the model file databases. +``` + + +##### `list(self)` \{#modelfiledatabase-list\} + +```text +Returns: + List of all models in the model file databases connection. +``` + + +##### `delete(self)` \{#modelfiledatabase-delete\} + +```text +deletes a models in the model file databases. +``` + + +##### `initialize(self, *args, **kwargs)` \{#modelfiledatabase-initialize\} + +```text +Creates a new file model database if it doesn't exist. +``` diff --git a/website/docs/api/services/model_file_database_connection.mdx b/website/docs/api/services/model_file_database_connection.mdx new file mode 100644 index 00000000..4a3aecdf --- /dev/null +++ b/website/docs/api/services/model_file_database_connection.mdx @@ -0,0 +1,160 @@ +--- +id: services.model_file_database_connection +title: tethysext.atcore.services.model_file_database_connection +sidebar_label: model_file_database_connection +--- + +# `tethysext.atcore.services.model_file_database_connection` + +```text +******************************************************************************** +* Name: model_file_database_connection.py +* Author: nswain & ckrewson +* Created On: December 05, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelFileDatabaseConnection(ModelDatabaseConnectionBase)` \{#modelfiledatabaseconnection\} + +```text +Represents a Model File Database. +``` +#### Methods + + +##### `__init__(self, db_dir, db_app_namespace=None, lock_timeout=5, poll_interval=0.05)` \{#modelfiledatabaseconnection-init\} + +```text +Constructor + +Args: + db_dir(str): Directory path to model file database. + db_app_namespace(str): App Namespace + lock_timeout(int): Time limit (seconds) for trying to acquire file lock before a TimeoutError + poll_interval(float): Time interval (seconds) between attempts to acquire file lock +``` + +*`@property`* + +##### `lock(self)` \{#modelfiledatabaseconnection-lock\} + +```text +Creates a file lock for the file database +``` + + +##### `list(self)` \{#modelfiledatabaseconnection-list\} + +```text +Returns: + List of files and directories in the model database. +``` + + +##### `delete(self, filename)` \{#modelfiledatabaseconnection-delete\} + +```text +Deletes a file from the model database. filename can be directory or file + +Args: + filename(str): File name or relative path to file that will be deleted. +``` + + +##### `add(self, filepath)` \{#modelfiledatabaseconnection-add\} + +```text +Adds a file or directory (from a filepath) to the model database. + +Args: + filepath(str): Absolute path to file that will be added to the model file database. + +Returns: + str: Path to location of file within model db. +``` + + +##### `add_zip_file(self, zip_file)` \{#modelfiledatabaseconnection-add-zip-file\} + +```text +Adds a zipped file (from a filepath) to the model database. + +Args: + zip_file(str): Absolute path to file that will be added to the model file database. + +Returns: + str: Path to location of file within model db. +``` + + +##### `duplicate(self, ex_filename, new_filename)` \{#modelfiledatabaseconnection-duplicate\} + +```text +Copies a file or directory to the model database. + +Args: + ex_filename(str): File name or relative path to file that will be duplicated. + new_filename(str): File name or relative path to file that will be created. + +Returns: + str: Path to location of file within model db. +``` + + +##### `move(self, ex_filename, new_filename)` \{#modelfiledatabaseconnection-move\} + +```text +Moves a file or directory to the model database. + +Args: + ex_filename(str): File name or relative path to file that will be moved. + new_filename(str): File name or relative path to new file. + +Returns: + str: Path to location of file within model db. +``` + + +##### `bulk_delete(self, filename_list)` \{#modelfiledatabaseconnection-bulk-delete\} + +```text +Bulk Deletes list of file or directories from the model database. + +Args: + filename_list(list): List of filenames or directories that will be deleted. +``` + + +##### `bulk_add(self, filepath_list)` \{#modelfiledatabaseconnection-bulk-add\} + +```text +Bulk Adds list of files or directories (from a filepath) to the model database. + +Args: + filepath_list(list): List of filepath for files that will be deleted to the database. +``` + + +##### `bulk_duplicate(self, filename_list)` \{#modelfiledatabaseconnection-bulk-duplicate\} + +```text +Bulk Copies list of files or directories to the model database. + +Args: + filename_list(str): List of tuples with existing file and new filename, + i.e [(ex_filename1, new_filename1),(ex_filename2, new_filename2)] +``` + + +##### `bulk_move(self, filename_list)` \{#modelfiledatabaseconnection-bulk-move\} + +```text +Moves a file or directory in the model database. + +Args: + filename_list(str): List of tuples with existing file and new filename, + i.e [(ex_filename1, new_filename1),(ex_filename2, new_filename2)] +``` diff --git a/website/docs/api/services/model_file_db_spatial_manager.mdx b/website/docs/api/services/model_file_db_spatial_manager.mdx new file mode 100644 index 00000000..4c644840 --- /dev/null +++ b/website/docs/api/services/model_file_db_spatial_manager.mdx @@ -0,0 +1,57 @@ +--- +id: services.model_file_db_spatial_manager +title: tethysext.atcore.services.model_file_db_spatial_manager +sidebar_label: model_file_db_spatial_manager +--- + +# `tethysext.atcore.services.model_file_db_spatial_manager` + +```text +******************************************************************************** +* Name: spatial_manager +* Author: nswain +* Created On: July 06, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `ModelFileDBSpatialManager(BaseSpatialManager)` \{#modelfiledbspatialmanager\} + +```text +Class for SpatialManagers using a file database +``` +#### Methods + +*`@abstractmethod`* + +##### `get_extent_for_project(self, *args, **kwargs)` \{#modelfiledbspatialmanager-get-extent-for-project\} + +```text +Return the extent / bounding box for a project/model. +``` + +*`@abstractmethod`* + +##### `get_projection_units(self, *args, **kwargs)` \{#modelfiledbspatialmanager-get-projection-units\} + +```text +Get units of the given projection. +``` + +*`@abstractmethod`* + +##### `get_projection_string(self, *args, **kwargs)` \{#modelfiledbspatialmanager-get-projection-string\} + +```text +Get the projection string as either wkt or proj4 format. + +Args: + model_db(ModelDatabase): the object representing the model database.: + srid(int): EPSG spatial reference identifier. + proj_format(str): project string format (either SpatialManager.PRO_WKT or SpatialManager.PRO_PROJ4). + +Returns: + str: projection string. +``` diff --git a/website/docs/api/services/paginate.mdx b/website/docs/api/services/paginate.mdx new file mode 100644 index 00000000..4545607d --- /dev/null +++ b/website/docs/api/services/paginate.mdx @@ -0,0 +1,34 @@ +--- +id: services.paginate +title: tethysext.atcore.services.paginate +sidebar_label: paginate +--- + +# `tethysext.atcore.services.paginate` + +```text +******************************************************************************** +* Name: pagintate.py +* Author: nswain +* Created On: April 17, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Functions + + +### `paginate(objects, results_per_page, page, result_name, sort_by_raw=None, sort_reversed=False)` \{#paginate\} + +```text +Paginate given list of objects. +Args: + objects(list): list of objects to paginate. + results_per_page(int): maximum number of results to show on a page. + page(int): page to view. + result_name(str): name to use when referencing the objects. + sort_by_raw(str): sort field if applicable. + sort_reversed(boo): indicates whether the sort is reversed or not. + +Returns: + list, dict: list of objects for current page, metadata form paginantion page. +``` diff --git a/website/docs/api/services/resource_condor_workflow.mdx b/website/docs/api/services/resource_condor_workflow.mdx new file mode 100644 index 00000000..6d59e914 --- /dev/null +++ b/website/docs/api/services/resource_condor_workflow.mdx @@ -0,0 +1,68 @@ +--- +id: services.resource_condor_workflow +title: tethysext.atcore.services.resource_condor_workflow +sidebar_label: resource_condor_workflow +--- + +# `tethysext.atcore.services.resource_condor_workflow` + +```text +******************************************************************************** +* Name: resource_condor_workflow.py +* Author: gagelarsen +* Created On: December 11, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceCondorWorkflow(object)` \{#resourcecondorworkflow\} + +```text +Helper class that prepares and submits the new project upload jobs and workflow. +``` +#### Methods + + +##### `__init__(self, app, user, workflow_name, workspace_path, resource_db_url, resource, scheduler, job_manager, status_keys=None, db_engine_kwargs=None, **kwargs)` \{#resourcecondorworkflow-init\} + +```text +Constructor. + +Args: + app (TethysApp): App class for the Tethys app. + user (auth.User): Django user. + workflow_name (str): Name of the job. + workspace_path (str): Path to workspace to be used by job. + resource_db_url (str): SQLAlchemy url to Resource database. + resource (Resource): Instance of the Resource. + scheduler (Scheduler): The condor scheduler for the application + job_manager (JobManger): The condor job manager for the application. + status_keys (list): One or more keys of statuses to check to determine resource status. The other jobs must update these statuses to one of the Resource.OK_STATUSES for the resource to be marked as SUCCESS. + db_engine_kwargs (dict): Optional arguments to pass to SQLAlchemy create_engine method. +``` + + +##### `get_jobs(self)` \{#resourcecondorworkflow-get-jobs\} + +```text +Get CondorWorkflowJobNodes and the corresponding status key. + +Returns: + list: A list of 2 tuples in the format [(CondorWorkflowJobNodes, 'status_key'), ...] +``` + + +##### `prepare(self)` \{#resourcecondorworkflow-prepare\} + +```text +Prepares all workflow jobs for processing upload to database. +``` + + +##### `run_job(self)` \{#resourcecondorworkflow-run-job\} + +```text +Executes the prepared job. +``` diff --git a/website/docs/api/services/resource_spatial_manager.mdx b/website/docs/api/services/resource_spatial_manager.mdx new file mode 100644 index 00000000..dcc29afb --- /dev/null +++ b/website/docs/api/services/resource_spatial_manager.mdx @@ -0,0 +1,102 @@ +--- +id: services.resource_spatial_manager +title: tethysext.atcore.services.resource_spatial_manager +sidebar_label: resource_spatial_manager +--- + +# `tethysext.atcore.services.resource_spatial_manager` + +```text +******************************************************************************** +* Name: resource_spatial_manager +* Author: nswain, msouffront & htran +* Created On: December 15, 2020 +* Updated on: December 15, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Classes + + +### `ResourceSpatialManager(BaseSpatialManager)` \{#resourcespatialmanager\} + +```text +A generic SpatialManger for SpatialResource. +``` +#### Methods + + +##### `get_projection_units(self, *args, **kwargs)` \{#resourcespatialmanager-get-projection-units\} + +```text +Get units of the given projection. +``` + + +##### `get_projection_string(self, *args, **kwargs)` \{#resourcespatialmanager-get-projection-string\} + +```text +Get the projection string as either wkt or proj4 format. +``` + + +##### `get_extent_layer_name(self, resource_id)` \{#resourcespatialmanager-get-extent-layer-name\} + +```text +Get name of the extent layer for a given resource. + +Args: + resource_id(str): id of the Resource. + +Returns: + str: name of the extent layer. +``` + + +##### `get_extent_for_project(self, datastore_name, resource_id)` \{#resourcespatialmanager-get-extent-for-project\} + +```text +Get the extent. This will return the list of the extent in EPSG 4326. +The query in resource_extent_layer_view transforms all features to 4326. + +Args: + datastore_name(str): name of the PostGIS datastore in GeoServer that contains the layer data. + For example: app_primary_db. + resource_id(str): id of the Resources. +``` + + +##### `get_resource_extent_wms_url(self, resource)` \{#resourcespatialmanager-get-resource-extent-wms-url\} + +```text +Get url for map preview image. +Returns: + str: preview image url. +``` + + +##### `create_extent_layer(self, datastore_name, resource_id, geometry_type=None, srid=4326)` \{#resourcespatialmanager-create-extent-layer\} + +```text +Creates a GeoServer SQLView Layer for the extent from the resource. + +Args: + datastore_name(str): name of the PostGIS datastore in GeoServer that contains the layer data. + For example: app_primary_db. + resource_id(str): id of the Resources. + geometry_type(str): type of geometry. Pick from: Polygon, LineString, Point. + srid(str): Spatial Reference Identifier of the extent. Default to 4326. +``` + + +##### `delete_extent_layer(self, datastore_name, resource_id, recurse=True)` \{#resourcespatialmanager-delete-extent-layer\} + +```text +Delete a given geoserver layer. + +Args: + datastore_name(str): name of the PostGIS datastore in GeoServer that contains the layer data. + For example: app_primary_db. + resource_id(str): id of the Resources. + recurse (bool): recursively delete any dependent objects if True. +``` diff --git a/website/docs/api/services/resource_workflows/_category_.json b/website/docs/api/services/resource_workflows/_category_.json new file mode 100644 index 00000000..e2d2436f --- /dev/null +++ b/website/docs/api/services/resource_workflows/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "resource_workflows", + "position": 26 +} diff --git a/website/docs/api/services/resource_workflows/decorators.mdx b/website/docs/api/services/resource_workflows/decorators.mdx new file mode 100644 index 00000000..aaea7904 --- /dev/null +++ b/website/docs/api/services/resource_workflows/decorators.mdx @@ -0,0 +1,22 @@ +--- +id: services.resource_workflows.decorators +title: tethysext.atcore.services.resource_workflows.decorators +sidebar_label: decorators +--- + +# `tethysext.atcore.services.resource_workflows.decorators` + +> _No description._ + +## Functions + + +### `workflow_step_controller(is_rest_controller=False)` \{#workflow-step-controller\} + +> _No description._ + + + +### `workflow_step_job(job_func=None, *, db_engine_kwargs=None)` \{#workflow-step-job\} + +> _No description._ diff --git a/website/docs/api/services/resource_workflows/helpers.mdx b/website/docs/api/services/resource_workflows/helpers.mdx new file mode 100644 index 00000000..bcc64143 --- /dev/null +++ b/website/docs/api/services/resource_workflows/helpers.mdx @@ -0,0 +1,31 @@ +--- +id: services.resource_workflows.helpers +title: tethysext.atcore.services.resource_workflows.helpers +sidebar_label: helpers +--- + +# `tethysext.atcore.services.resource_workflows.helpers` + +> _No description._ + +## Functions + + +### `set_step_status(resource_db_session, step, status)` \{#set-step-status\} + +```text +Sets the status on the provided step to the provided status. +Args: + resource_db_session(sqlalchemy.orm.Session): Session bound to the step. + step(ResourceWorkflowStep): The step to modify + status(str): The status to set. +``` + + +### `parse_workflow_step_args()` \{#parse-workflow-step-args\} + +```text +Parses and validates command line arguments for workflow_step_job. +Returns: + argparse.Namespace: The parsed and validated arguments. +``` diff --git a/website/docs/api/services/resource_workflows/index.mdx b/website/docs/api/services/resource_workflows/index.mdx new file mode 100644 index 00000000..443f1387 --- /dev/null +++ b/website/docs/api/services/resource_workflows/index.mdx @@ -0,0 +1,15 @@ +--- +id: services.resource_workflows.index +title: tethysext.atcore.services.resource_workflows +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.services.resource_workflows` + +> _No description._ + +## Modules + +- [`decorators`](./decorators.mdx) +- [`helpers`](./helpers.mdx) diff --git a/website/docs/api/services/spatial_reference.mdx b/website/docs/api/services/spatial_reference.mdx new file mode 100644 index 00000000..f45bda17 --- /dev/null +++ b/website/docs/api/services/spatial_reference.mdx @@ -0,0 +1,70 @@ +--- +id: services.spatial_reference +title: tethysext.atcore.services.spatial_reference +sidebar_label: spatial_reference +--- + +# `tethysext.atcore.services.spatial_reference` + +```text +******************************************************************************** +* Name: spatial_reference.py +* Author: nswain +* Created On: May 14, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Classes + + +### `SpatialReferenceService` \{#spatialreferenceservice\} + +```text +Used to lookup available spatial references dynamically. +``` +#### Methods + + +##### `__init__(self, db_engine)` \{#spatialreferenceservice-init\} + +```text +constructor. +Args: + db_engine(sqlalchemy.engine): engine with connection to spatial database with spatial_ref_sys table. +``` + + +##### `get_spatial_reference_system_by_srid(self, srid)` \{#spatialreferenceservice-get-spatial-reference-system-by-srid\} + +```text +" +Get a user friendly name for spatial reference system based on an SRID. + +Args: + srid(str): EPSG spatial reference id as a string (e.g. 3566). +``` + + +##### `get_wkt_by_srid(self, srid)` \{#spatialreferenceservice-get-wkt-by-srid\} + +```text +" +Get well known text for spatial reference system based on an SRID. + +Args: + srid(str): EPSG spatial reference id as a string (e.g. 3566). + +Returns: + dict: JSON with the well known text for the spatial reference ID if found (else empty string) +``` + + +##### `get_spatial_reference_system_by_query_string(self, query_words)` \{#spatialreferenceservice-get-spatial-reference-system-by-query-string\} + +```text +" +Get a user friendly name for spatial reference system based on a query string. + +Args: + query_words(list): list of query parameters (e.g. ['Utah', 'Central'] ). +``` diff --git a/website/docs/api/services/workflow_manager/_category_.json b/website/docs/api/services/workflow_manager/_category_.json new file mode 100644 index 00000000..fc7e2f50 --- /dev/null +++ b/website/docs/api/services/workflow_manager/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "workflow_manager", + "position": 27 +} diff --git a/website/docs/api/services/workflow_manager/base_workflow_manager.mdx b/website/docs/api/services/workflow_manager/base_workflow_manager.mdx new file mode 100644 index 00000000..da9f837c --- /dev/null +++ b/website/docs/api/services/workflow_manager/base_workflow_manager.mdx @@ -0,0 +1,62 @@ +--- +id: services.workflow_manager.base_workflow_manager +title: tethysext.atcore.services.workflow_manager.base_workflow_manager +sidebar_label: base_workflow_manager +--- + +# `tethysext.atcore.services.workflow_manager.base_workflow_manager` + +> _No description._ + +## Classes + + +### `BaseWorkflowManager(object)` \{#baseworkflowmanager\} + +> _No description._ + +#### Methods + + +##### `__init__(self, session, model_db, resource_workflow_step, user, working_directory, app, scheduler_name=None, jobs=None, job_script=None, input_files=None, gs_engine=None, *args)` \{#baseworkflowmanager-init\} + +```text +Constructor. + +Args: + session(sqlalchemy.orm.Session): An SQLAlchemy session bound to the resource workflow. + model_db(ModelDatabase): ModelDatabase instance bound to model database. + resource_workflow_step(atcore.models.app_users.ResourceWorkflowStep): Instance of ResourceWorkflowStep. Note: Must have active session (i.e. not closed). + user(auth.User): The Django user submitting the job. + working_directory(str): Path to users's workspace. + app(TethysAppBase): Class or instance of an app. + scheduler_name(str): Name of the condor scheduler to use. + jobs(list<CondorWorkflowJobNode or dict>): List of CondorWorkflowJobNodes to run. + input_files(list<str>): List of paths to files to sends as inputs to every job. Optional. +``` + +*`@property`* + +##### `workspace(self)` \{#baseworkflowmanager-workspace\} + +```text +Workspace path property. +Returns: + str: Path to workspace for this workflow +``` + + +##### `prepare(self)` \{#baseworkflowmanager-prepare\} + +> _No description._ + + + +##### `run_job(self)` \{#baseworkflowmanager-run-job\} + +```text +Prepares and executes the job. + +Returns: + str: UUID of the Workflow/Job. +``` diff --git a/website/docs/api/services/workflow_manager/condor_workflow_manager.mdx b/website/docs/api/services/workflow_manager/condor_workflow_manager.mdx new file mode 100644 index 00000000..cc3ef6e2 --- /dev/null +++ b/website/docs/api/services/workflow_manager/condor_workflow_manager.mdx @@ -0,0 +1,94 @@ +--- +id: services.workflow_manager.condor_workflow_manager +title: tethysext.atcore.services.workflow_manager.condor_workflow_manager +sidebar_label: condor_workflow_manager +--- + +# `tethysext.atcore.services.workflow_manager.condor_workflow_manager` + +```text +******************************************************************************** +* Name: condor_workflow_manager.py +* Author: nswain +* Created On: March 13, 2019 +* Copyright: (c) Aquaveo 2019 +******************************************************************************** +``` +## Classes + + +### `ResourceWorkflowCondorJobManager(BaseWorkflowManager)` \{#resourceworkflowcondorjobmanager\} + +```text +Helper class that prepares and submits condor workflows/jobs for resource workflows. +``` +#### Methods + + +##### `__init__(self, session, resource, resource_workflow_step, user, working_directory, app, scheduler_name, jobs=None, input_files=None, gs_engine=None, resource_workflow=None, workflow_kwargs=None, *args)` \{#resourceworkflowcondorjobmanager-init\} + +```text +Constructor. + +Args: + session(sqlalchemy.orm.Session): An SQLAlchemy session bound to the resource workflow. + resource(Resource): The resource being processed. + resource_workflow_step(atcore.models.app_users.ResourceWorkflowStep): Instance of ResourceWorkflowStep. Note: Must have active session (i.e. not closed). + user(auth.User): The Django user submitting the job. + working_directory(str): Path to users's workspace. + app(TethysAppBase): Class or instance of an app. + scheduler_name(str): Name of the condor scheduler to use. + jobs(list<CondorWorkflowJobNode or dict>): List of CondorWorkflowJobNodes to run. + input_files(list<str>): List of paths to files to sends as inputs to every job. Optional. + resource_workflow(ResourceWorkflow): The workflow. + workflow_kwargs(dict): Optional keyword arguments to pass to the CondorWorkflow. +``` + +*`@property`* + +##### `workspace(self)` \{#resourceworkflowcondorjobmanager-workspace\} + +```text +Workspace path property. +Returns: + str: Path to workspace for this workflow +``` + + +##### `prepare(self)` \{#resourceworkflowcondorjobmanager-prepare\} + +```text +Prepares all workflow jobs for processing upload to database. + +Returns: + int: the job id. +``` + + +##### `run_job(self)` \{#resourceworkflowcondorjobmanager-run-job\} + +```text +Prepares and executes the job. + +Returns: + str: UUID of the CondorWorkflow. +``` + + +##### `validate_jobs(self, jobs)` \{#resourceworkflowcondorjobmanager-validate-jobs\} + +```text +Validate the given jobs. + +Conditions: +1. Jobs must be defined (not None or empty) +2. Jobs must be either: + - a function, or + - a list of CondorWorkflowJobNode objects, equivalent dictionary, or a mix of both + +Args: + jobs(function | list<CondorWorkflowJobNode or dict>): The jobs to validate. + +Raises: + ValueError: If jobs is None, empty, or not of a valid type. +``` diff --git a/website/docs/api/services/workflow_manager/index.mdx b/website/docs/api/services/workflow_manager/index.mdx new file mode 100644 index 00000000..562d2e72 --- /dev/null +++ b/website/docs/api/services/workflow_manager/index.mdx @@ -0,0 +1,15 @@ +--- +id: services.workflow_manager.index +title: tethysext.atcore.services.workflow_manager +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.services.workflow_manager` + +> _No description._ + +## Modules + +- [`base_workflow_manager`](./base_workflow_manager.mdx) +- [`condor_workflow_manager`](./condor_workflow_manager.mdx) diff --git a/website/docs/api/urls/_category_.json b/website/docs/api/urls/_category_.json new file mode 100644 index 00000000..54ecbb25 --- /dev/null +++ b/website/docs/api/urls/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "urls", + "position": 28 +} diff --git a/website/docs/api/urls/app_users.mdx b/website/docs/api/urls/app_users.mdx new file mode 100644 index 00000000..8494d901 --- /dev/null +++ b/website/docs/api/urls/app_users.mdx @@ -0,0 +1,62 @@ +--- +id: urls.app_users +title: tethysext.atcore.urls.app_users +sidebar_label: app_users +--- + +# `tethysext.atcore.urls.app_users` + +```text +******************************************************************************** +* Name: app_users.py +* Author: nswain +* Created On: November 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Functions + + +### `urls(url_map_maker, app, persistent_store_name, base_url_path='', base_template='atcore/app_users/base.html', custom_controllers=(), custom_models=(), custom_resources=(), custom_permissions_manager=None)` \{#urls\} + +```text +Generate UrlMap objects for app_users extension. To link to pages provided by the app_users extension use the name of the url with your app namespace: + +:: + + {% url 'my_first_app:app_users_add_user %} + {% url 'my_first_app:app_users_edit_user, user_id=user.id %} + +Args: + url_map_maker (UrlMap): UrlMap class bound to app root url. + app (TethysAppBase): instance of Tethys app class. + persistent_store_name (str): name of persistent store database setting the controllers should use to create sessions. + base_url_path (str): url path to prepend to all app_user urls (e.g.: 'foo/bar'). + base_template (str): relative path to base template (e.g.: 'my_first_app/base.html'). Useful for customizing styles or overriding navigation of all views. + custom_controllers (list<TethysController>): Any number of TethysController subclasses to override default controller classes. + custom_models (list<cls>): custom subclasses of AppUser or Organization models. + custom_resources (list<Resource> or dict<Resource: list<TethysController>>): list of custom subclasses of Resource models or dict with Resource models as keys and list of controllers as values. + custom_permissions_manager (cls): Custom AppPermissionsManager class. Defaults to AppPermissionsManager. + +Url Map Names: + app_users_manage_users + app_users_add_user + app_users_edit_user <user_id> + app_users_add_existing_user + app_users_user_account + app_users_manage_organizations + app_users_manage_organization_members <organization_id> + app_users_new_organization + app_users_edit_organization <organization_id> + +Url Map Names for each Resource given: + <resource_slug>_manage_resources + <resource_slug>_new_resource + <resource_slug>_edit_resource <resource_id> + <resource_slug>_resource_details <resource_id> + <resource_slug>_resource_status <resource_id> + <resource_slug>_resource_status_list + +Returns: + tuple: UrlMap objects for the app_users extension. +``` diff --git a/website/docs/api/urls/index.mdx b/website/docs/api/urls/index.mdx new file mode 100644 index 00000000..5c9c79de --- /dev/null +++ b/website/docs/api/urls/index.mdx @@ -0,0 +1,17 @@ +--- +id: urls.index +title: tethysext.atcore.urls +sidebar_label: Overview +sidebar_position: 0 +--- + +# `tethysext.atcore.urls` + +> _No description._ + +## Modules + +- [`app_users`](./app_users.mdx) +- [`resource_workflows`](./resource_workflows.mdx) +- [`resources`](./resources.mdx) +- [`spatial_reference`](./spatial_reference.mdx) diff --git a/website/docs/api/urls/resource_workflows.mdx b/website/docs/api/urls/resource_workflows.mdx new file mode 100644 index 00000000..0b81ce9f --- /dev/null +++ b/website/docs/api/urls/resource_workflows.mdx @@ -0,0 +1,50 @@ +--- +id: urls.resource_workflows +title: tethysext.atcore.urls.resource_workflows +sidebar_label: resource_workflows +--- + +# `tethysext.atcore.urls.resource_workflows` + +```text +******************************************************************************** +* Name: resource_workflows.py +* Author: nswain +* Created On: November 19, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Functions + + +### `urls(url_map_maker, app, persistent_store_name, workflow_pairs, base_url_path='', custom_models=(), custom_permissions_manager=None, base_template='atcore/base.html', handler=DEFAULT_HANDLER['handler'], handler_type=DEFAULT_HANDLER['type'])` \{#urls\} + +```text +Generate UrlMap objects for each workflow model-controller pair provided. To link to pages provided by the app_users extension use the name of the url with your app namespace: + +:: + + {% url 'my_first_app:a_workflow_workflow', resource_id=resource.id, workflow_id=workflow.id %} + + OR + + reverse('my_first_app:a_workflow_workflow_step', kwargs={'resource_id': resource.id, 'workflow_id': workflow.id, 'step_id': step.id}) + +Args: + url_map_maker(UrlMap): UrlMap class bound to app root url. + app(TethysAppBase): instance of Tethys app class. + persistent_store_name(str): name of persistent store database setting the controllers should use to create sessions. + workflow_pairs(2-tuple<ResourceWorkflow, ResourceWorkflowRouter>): Pairs of ResourceWorkflow models and ResourceWorkFlow views. + base_url_path(str): url path to prepend to all app_user urls (e.g.: 'foo/bar'). + custom_models(list<cls>): custom subclasses of AppUser, Organization, or Resource models. + custom_permissions_manager(cls): Custom AppPermissionsManager class. Defaults to AppPermissionsManager. + base_template(str): relative path to base template (e.g.: 'my_first_app/base.html'). Useful for customizing styles or overriding navigation of all views. + +Url Map Names: + <workflow_type>_workflow <resource_id> <workflow_id> + <workflow_type>_workflow_step <resource_id> <workflow_id> <step_id> + <workflow_type>_workflow_step_result <resource_id> <workflow_id> <step_id> <result_id> + +Returns: + tuple: UrlMap objects for the app_users extension. +``` diff --git a/website/docs/api/urls/resources.mdx b/website/docs/api/urls/resources.mdx new file mode 100644 index 00000000..e4b93927 --- /dev/null +++ b/website/docs/api/urls/resources.mdx @@ -0,0 +1,51 @@ +--- +id: urls.resources +title: tethysext.atcore.urls.resources +sidebar_label: resources +--- + +# `tethysext.atcore.urls.resources` + +```text +******************************************************************************** +* Name: resources.py +* Author: msouffront & htran +* Created On: November 20, 2020 +* Copyright: (c) Aquaveo 2020 +******************************************************************************** +``` +## Functions + + +### `urls(url_map_maker, app, persistent_store_name, base_url_path='', base_template='atcore/app_users/base.html', custom_controllers=(), custom_models=(), custom_permissions_manager=None, resource_model=Resource)` \{#urls\} + +```text +Generate UrlMap objects for Resource Views. To link to pages provided by the Resource Views use the name of the url with your app namespace: + +:: + + {% url 'my_first_app:manage_resources_url %} + {% url 'my_first_app:edit_resource_url, resource_id=resource.id %} + +Args: + url_map_maker (UrlMap): UrlMap class bound to app root url. + app (TethysAppBase): instance of Tethys app class. + persistent_store_name (str): name of persistent store database setting the controllers should use to create sessions. + base_url_path (str): url path to prepend to all app_user urls (e.g.: 'foo/bar'). + base_template (str): relative path to base template (e.g.: 'my_first_app/base.html'). Useful for customizing styles or overriding navigation of all views. + custom_controllers (list<TethysController>): Any number of TethysController subclasses to override default controller classes. + custom_models (list<cls>): custom subclasses of AppUser or Organization models. + custom_permissions_manager (cls): Custom AppPermissionsManager class. Defaults to AppPermissionsManager. + resource_model (Resource): Resource model class. Defaults to Resource. + +Url Map Names: + <resource_slug>_manage_resources + <resource_slug>_new_resource + <resource_slug>_edit_resource <resource_id> + <resource_slug>_resource_details <resource_id> + <resource_slug>_resource_status <resource_id> + <resource_slug>_resource_status_list + +Returns: + tuple: UrlMap objects for the Resource Views. +``` diff --git a/website/docs/api/urls/spatial_reference.mdx b/website/docs/api/urls/spatial_reference.mdx new file mode 100644 index 00000000..d9de0932 --- /dev/null +++ b/website/docs/api/urls/spatial_reference.mdx @@ -0,0 +1,31 @@ +--- +id: urls.spatial_reference +title: tethysext.atcore.urls.spatial_reference +sidebar_label: spatial_reference +--- + +# `tethysext.atcore.urls.spatial_reference` + +> _No description._ + +## Functions + + +### `urls(url_map_maker, app, persistent_store_name, base_url_path='', custom_controllers=(), custom_services=())` \{#urls\} + +```text +Generate UrlMap objects for spatial reference REST endpoints. + +Args: + url_map_maker(UrlMap): UrlMap class bound to app root url. + app(TethysAppBase): instance of Tethys app class. + persistent_store_name(str): name of persistent store database setting the controllers should use to create sessions. + custom_controllers(list<TethysController>): Any number of TethysController subclasses to override default controller classes. + custom_services(cls): custom subclasses of SpatialReferenceService service. + +Url Map Names: + atcore_query_spatial_reference + +Returns: + tuple: UrlMap objects for the spatial reference gizmo. +``` diff --git a/website/docs/api/utilities.mdx b/website/docs/api/utilities.mdx new file mode 100644 index 00000000..ad6bb2bb --- /dev/null +++ b/website/docs/api/utilities.mdx @@ -0,0 +1,82 @@ +--- +id: utilities +title: tethysext.atcore.utilities +sidebar_label: utilities +--- + +# `tethysext.atcore.utilities` + +```text +******************************************************************************** +* Name: utilities +* Author: nswain +* Created On: July 30, 2018 +* Copyright: (c) Aquaveo 2018 +******************************************************************************** +``` +## Functions + + +### `parse_url(url)` \{#parse-url\} + +```text +Splits url into parts. +e.g.: "http://admin:geoserver@localhost:8181/geoserver/rest" +``` + + +### `generate_geoserver_urls(gs_engine)` \{#generate-geoserver-urls\} + +> _No description._ + + + +### `clean_request(request)` \{#clean-request\} + +```text +Strip the "method" variable from the GET and POST params of a request. + +Args: + request(HttpRequest): the request. + +Returns: + HttpRequest: the modified request +``` + + +### `strip_list(the_list, *args)` \{#strip-list\} + +```text +Strip empty items from end of list. + +Args: + the_list(list): the list. + *args: any number of values to strip from the end of the list. +``` + + +### `grammatically_correct_join(strings, conjunction='and')` \{#grammatically-correct-join\} + +> _No description._ + + + +### `import_from_string(path)` \{#import-from-string\} + +```text +Import object from given dot-path string. + +Args: + path<str>: Dot-path to Class, Function or other object in a module (e.g. foo.bar.Klass). +``` + + +### `json_serializer(obj)` \{#json-serializer\} + +> _No description._ + + + +### `update_urlmap_index(urlmaps, app)` \{#update-urlmap-index\} + +> _No description._ diff --git a/website/docs/concepts/_category_.json b/website/docs/concepts/_category_.json new file mode 100644 index 00000000..50a638da --- /dev/null +++ b/website/docs/concepts/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Concepts", + "position": 2 +} diff --git a/website/docs/concepts/app-users.md b/website/docs/concepts/app-users.md new file mode 100644 index 00000000..08e159c7 --- /dev/null +++ b/website/docs/concepts/app-users.md @@ -0,0 +1,89 @@ +--- +id: concepts-app-users +title: App Users and Organizations +sidebar_label: App Users +sidebar_position: 2 +--- + +# App users and organizations + +The `app_users` system is atcore's identity model. It sits next to Django's auth users and adds an app-scoped layer for membership, roles, and licensing. + +## The core trio + +The models live under [`tethysext.atcore.models.app_users`](../api/models/app_users/index.mdx): + +- [`AppUser`](../api/models/app_users/app_user.mdx#appuser) — one row per app-aware user. Each `AppUser.username` maps to a Django user. Stores a `role` and an `is_active` flag, plus a list of `organizations` and `settings`. +- [`Organization`](../api/models/app_users/organization.mdx) — a group that owns resources. Has a `license`, `members` (`AppUser`s), and `resources`. +- [`Resource`](../api/models/app_users/resource.mdx#resource) — a domain object owned by one or more organizations. See [Resources](./resources.md). + +These three share `AppUsersBase` ([`models.app_users.base`](../api/models/app_users/base.mdx)), a SQLAlchemy declarative base that you bind to your app's app-users persistent store. + +## Roles and licenses + +Roles ([`Roles`](../api/services/app_users/roles.mdx)) are user-level — what is this person allowed to do? + +```python +from tethysext.atcore.services.app_users.roles import Roles + +Roles.ORG_USER # 'user_role_org_user' +Roles.ORG_REVIEWER # 'user_role_org_reviewer' +Roles.ORG_ADMIN # 'user_role_org_admin' +Roles.APP_ADMIN # 'user_role_app_admin' +Roles.DEVELOPER # 'user_role_developer' +``` + +Licenses ([`Licenses`](../api/services/app_users/licenses.mdx)) are organization-level — what tier of features did this organization buy? + +```python +from tethysext.atcore.services.app_users.licenses import Licenses + +Licenses.STANDARD +Licenses.ADVANCED +Licenses.PROFESSIONAL +Licenses.CONSULTANT +``` + +A user's effective permissions are the cross-product of their role and the licenses of the organizations they belong to. The [`AppPermissionsManager`](../api/services/app_users/permissions_manager.mdx#apppermissionsmanager) computes the permission-group name for any (role, license) pair, namespaced to your app. + +See the [Permissions concept page](./permissions.md) for the full matrix. + +## Initializing the database + +Call [`initialize_app_users_db`](../api/models/app_users/initializer.mdx) from your Tethys app's `persistent_store_initializer` to create the tables and seed the developer/staff user: + +```python +# example — app.py +from tethysext.atcore.models.app_users import initialize_app_users_db + +def init_app_users_db(engine, first_time): + initialize_app_users_db(engine, first_time=first_time) +``` + +If you've subclassed `AppUser`, pass it via `app_user_model=MyAppUser`. + +## Wiring the URLs + +Register the app-user CRUD pages from [`tethysext.atcore.urls.app_users`](../api/urls/app_users.mdx): + +```python +# example — app.py register_url_maps +from tethysext.atcore.urls import app_users as app_users_urls + +url_maps = app_users_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='app_users_db', + base_template='my_first_app/base.html', +) +``` + +This emits URL maps like `app_users_manage_users`, `app_users_add_user`, `app_users_manage_organizations`, etc. — the full list is in the docstring at [`urls.app_users`](../api/urls/app_users.mdx). + +## Subclassing models + +You can extend `AppUser`, `Organization`, and `Resource` and pass your subclasses into `urls(..., custom_models=[MyAppUser, MyOrganization])`. The matching `Modify*` and `Manage*` controllers will pick them up. + +:::tip +For most apps the right level of customization is **Resource** (custom fields, polymorphic types). Subclassing `AppUser` or `Organization` is rarer — the defaults already cover username/role/membership. +::: diff --git a/website/docs/concepts/controllers.md b/website/docs/concepts/controllers.md new file mode 100644 index 00000000..21e9df53 --- /dev/null +++ b/website/docs/concepts/controllers.md @@ -0,0 +1,127 @@ +--- +id: concepts-controllers +title: Controllers +sidebar_label: Controllers +sidebar_position: 5 +--- + +# Controllers + +atcore ships class-based Tethys controllers (`TethysController` subclasses) that handle the recurring patterns: pages bound to an `AppUser` + `Resource`, map views, workflow views, and admin pages for app users / organizations / resources. + +## The base hierarchy + +The fundamental base view is [`ResourceView`](../api/controllers/resource_view.mdx#resourceview). It does three things on every request: + +1. Wraps `get` and `post` with [`active_user_required`](../api/services/app_users/decorators.mdx) and [`resource_controller`](../api/services/app_users/decorators.mdx). The first redirects unauthenticated or disabled users; the second resolves `resource_id` from the URL into a `Resource` instance and opens a SQLAlchemy session. +2. Calls subclass hooks: `on_get` (pre-GET hook), `get_context` (extend the template context), and `request_to_method` (POST routes through this — it derives the handler from a `method` POST/GET parameter and dispatches to that named method on the class). +3. Renders `template_name` with a context that includes the `resource`, `back_url`, `base_template`, and any extras from `get_context`. + +To build a resource-aware page, subclass `ResourceView`, set `template_name`, and override `get_context` (and optionally `on_post`). + +## MapView + +[`MapView`](../api/controllers/map_view.mdx#mapview) extends `ResourceView` and produces a Tethys map page driven by a `MapManager` and `SpatialManager` pair (see [Services](./services.md)). It expects two class attributes set by the subclass: + +- `_MapManager` — your `MapManager` subclass. +- `_SpatialManager` — your `SpatialManager` subclass (typically derived from [`BaseSpatialManager`](../api/services/base_spatial_manager.mdx)). + +`MapView` calls `map_manager.compose_map(...)` to build the map, applies layout tweaks (sets the map height/width, disables the default legend), and exposes hooks for adding custom layers, plots, and toolbar items. + +## App-user CRUD pages + +Under [`controllers.app_users`](../api/controllers/app_users/index.mdx): + +- [`ManageUsers`](../api/controllers/app_users/manage_users.mdx), [`ModifyUser`](../api/controllers/app_users/modify_user.mdx), [`AddExistingUser`](../api/controllers/app_users/add_existing_user.mdx), [`UserAccount`](../api/controllers/app_users/user_account.mdx) — user management. +- [`ManageOrganizations`](../api/controllers/app_users/manage_organizations.mdx), [`ModifyOrganization`](../api/controllers/app_users/modify_organization.mdx), [`ManageOrganizationMembers`](../api/controllers/app_users/manage_organization_members.mdx) — org management. +- [`ManageResources`](../api/controllers/app_users/manage_resources.mdx), [`ModifyResource`](../api/controllers/app_users/modify_resource.mdx), [`ResourceDetails`](../api/controllers/app_users/resource_details.mdx), [`ResourceStatus`](../api/controllers/app_users/resource_status.mdx) — resource management. + +Wire them via the `urls(...)` helpers — see [App Users](./app-users.md) and [Resources](./resources.md). + +The mixins these views compose with — `AppUsersViewMixin`, `ResourceBackUrlViewMixin`, `ResourceViewMixin`, `MultipleResourcesViewMixin` — are documented in [`controllers.app_users.mixins`](../api/controllers/app_users/mixins.mdx). + +## REST controllers + +Under [`controllers.rest`](../api/controllers/rest/index.mdx): + +- [`QuerySpatialReference`](../api/controllers/rest/spatial_reference.mdx) — backs the [`SpatialReferenceSelect`](./gizmos.md) gizmo with EPSG lookups against `spatial_ref_sys`. + +## Workflow controllers + +See [Resource Workflows](./resource-workflows.md). The router is [`ResourceWorkflowRouter`](../api/controllers/resource_workflows/resource_workflow_router.mdx#resourceworkflowrouter); the base view is [`ResourceWorkflowView`](../api/controllers/resource_workflows/workflow_view.mdx). + +## Tabbed resource details + +[`TabbedResourceDetails`](../api/controllers/resources/tabbed_resource_details.mdx) renders a tabbed page composed of tab classes from [`controllers.resources.tabs`](../api/controllers/resources/tabs/index.mdx). Use it instead of `ResourceDetails` when one detail page isn't enough. + +Built-in tabs: + +- [`ResourceSummaryTab`](../api/controllers/resources/tabs/summary_tab.mdx) — name / status / created-by header card. Override `get_summary_tab_info` to add columns. +- [`ResourceFilesTab`](../api/controllers/resources/tabs/files_tab.mdx) — listing of `FileCollection`s attached to the resource. +- [`ResourceWorkflowsTab`](../api/controllers/resources/tabs/workflows_tab.mdx) — list of `ResourceWorkflow`s plus a "new workflow" launcher. Override `get_workflow_types` to filter the launcher menu by resource state. +- [`ResourceListTab`](../api/controllers/resources/tabs/resource_list_tab.mdx) — child resources for a parent resource. + +Compose them on a subclass: + +```python +# tethysapp/myapp/controllers/resources/project_details.py +from tethysext.atcore.controllers.resources import ( + TabbedResourceDetails, ResourceSummaryTab, + ResourceFilesTab, ResourceWorkflowsTab, +) + + +class ProjectSummaryTab(ResourceSummaryTab): + def get_summary_tab_info(self, request, session, resource): + return { + 'general': { + 'title': 'General', + 'columns': [ + [('Region', resource.get_attribute('region') or '-')], + [('Inputs', resource.get_attribute('input_count') or 0)], + ], + }, + } + + +class ProjectDetails(TabbedResourceDetails): + template_name = 'myapp/project_details.html' + tabs = ( + {'slug': 'summary', 'title': 'Summary', 'view': ProjectSummaryTab}, + {'slug': 'files', 'title': 'Files', 'view': ResourceFilesTab}, + {'slug': 'workflows', 'title': 'Workflows', 'view': ResourceWorkflowsTab}, + ) +``` + +`TabbedResourceDetails` needs a `{tab_slug}` URL kwarg, which the default `urls.resources.urls(...)` helpers don't emit. Register the URL by hand: + +```python +# tethysapp/myapp/app.py — register_url_maps +url_maps += [ + UrlMap( + name='project_details_tab', + url='projects/{resource_id}/{tab_slug}', + controller=ProjectDetails.as_controller( + _app=self, + _persistent_store_name='primary_db', + _AppUser=MyAppUser, + _Organization=MyOrganization, + _Resource=Project, + _PermissionsManager=MyPermissionsManager, + ), + ), +] +``` + +The leading-underscore kwargs to `as_controller(...)` populate the view mixin slots — `AppUsersViewMixin._AppUser`, `ResourceViewMixin._Resource`, and the rest. atcore's URL helpers fill them automatically; if you register a URL by hand, you fill them yourself. + +See [Add a tabbed resource details page](../how-to/add-a-tabbed-resource-details-page.md) for the full recipe. + +## Decorators + +Most of the time you inherit auth/session handling from `ResourceView`. For free-function controllers that still need atcore's behavior, the building blocks live in [`services.app_users.decorators`](../api/services/app_users/decorators.mdx): + +- `active_user_required()` +- `resource_controller(is_rest_controller=False)` + +For permission checks themselves, use Tethys's own `permission_required` / `has_permission` from `tethys_sdk.permissions`. diff --git a/website/docs/concepts/file-database.md b/website/docs/concepts/file-database.md new file mode 100644 index 00000000..149aa61c --- /dev/null +++ b/website/docs/concepts/file-database.md @@ -0,0 +1,166 @@ +--- +id: concepts-file-database +title: File Database +sidebar_label: File Database +sidebar_position: 9 +--- + +# File database + +A SQL-tracked filesystem store. Use it when a `Resource` needs to own files on disk and you want to look them up through the database instead of walking directories. + +## Anatomy + +Models in [`tethysext.atcore.models.file_database`](../api/models/file_database/index.mdx): + +- [`FileDatabase`](../api/models/file_database/file_database-module.mdx) — the top-level container. Carries a `meta` JSON blob and corresponds to a single root directory on disk. +- [`FileCollection`](../api/models/file_database/file_collection.mdx) — a named bucket of files within a `FileDatabase`. Backed by a UUID-named subdirectory. +- [`ResourceFileCollectionAssociation`](../api/models/file_database/resource_file_collection_association.mdx) — link table tying a `Resource` to one or more `FileCollection`s. + +Clients in [`tethysext.atcore.services.file_database`](../api/services/file_database.mdx): + +- [`FileDatabaseClient`](../api/services/file_database.mdx#filedatabaseclient) — bind / create file databases. +- [`FileCollectionClient`](../api/services/file_database.mdx#filecollectionclient) — bind / create file collections within a database. + +## When to use it + +- A `Resource` owns input files (uploaded shapefiles, rasters, parameter files) that need to live on disk because they're large or non-relational. +- A workflow step produces output files that should remain accessible after the step finishes. +- You want a uniform listing UI for "all the files this resource owns" via [`ResourceFilesTab`](../api/controllers/resources/tabs/files_tab.mdx). + +If you only need a single root directory of free-form files and don't care about per-collection grouping, fall back to the Tethys app workspace. Use `FileDatabase` when you want SQL to remember which collection each file belongs to and which resource owns each collection. + +## Creating a file database + +```python +# example — services +from tethysext.atcore.services.file_database import FileDatabaseClient + +# Create on disk + in DB +client = FileDatabaseClient.new( + session=session, + root_directory='/var/lib/myapp/file_dbs', + meta={'project': 'demo'}, +) + +# ...later, bind to an existing one by id: +client = FileDatabaseClient( + session=session, + root_directory='/var/lib/myapp/file_dbs', + file_database_id=existing_id, +) +``` + +On-disk layout: `<root_directory>/<file_database_id>/`. `write_meta()` persists the `meta` dict to a sidecar file alongside the database directory. + +## Working with collections + +```python +# example — services +from tethysext.atcore.services.file_database import FileCollectionClient + +collection_client = FileCollectionClient.new( + session=session, + file_database_client=client, + meta={'kind': 'inputs'}, +) +collection_client.add_item('/tmp/some_input.txt') +items = list(collection_client.files) +``` + +Exceptions are listed in the [exceptions reference](../reference/exceptions.md): + +- `UnboundFileDatabaseError`, `UnboundFileCollectionError` — operating on a deleted client. +- `FileDatabaseNotFoundError`, `FileCollectionNotFoundError` — the requested id doesn't exist. +- `FileCollectionItemNotFoundError`, `FileCollectionItemAlreadyExistsError` — item-level mismatches. + +## Wiring a Resource to its files + +Two mixins help your `Resource` subclass own collections: + +- [`FileCollectionMixin`](../api/mixins/file_collection_mixin.mdx) — model-level helper methods. +- [`FileCollectionsControllerMixin`](../api/mixins/file_collection_controller_mixin.mdx) — controller-level helpers for upload/download views. + +Neither mixin is re-exported from `tethysext.atcore.mixins.__init__` — a comment in the source warns "DO NOT IMPORT ... CAUSES CIRCULAR IMPORT ISSUES". Import them directly from their submodules: + +```python +from tethysext.atcore.mixins.file_collection_mixin import FileCollectionMixin +from tethysext.atcore.mixins.file_collection_controller_mixin import FileCollectionsControllerMixin +``` + +### Pattern A: per-resource `FileDatabase` + +When a resource owns a whole tree of files (e.g., a `Project` with many input files and output collections), give it its own `FileDatabase` and grow `FileCollection`s underneath: + +```python +# myapp_adapter/resources/project.py +import os +from sqlalchemy import Column, ForeignKey +from sqlalchemy.orm import relationship +from tethysext.atcore.models.app_users import Resource +from tethysext.atcore.models.file_database import FileDatabase +from tethysext.atcore.models.types.guid import GUID +from tethysext.atcore.services.file_database import FileDatabaseClient + + +class Project(Resource): + TYPE = 'project' + __mapper_args__ = {'polymorphic_identity': TYPE} + + file_database_id = Column(GUID, ForeignKey('file_databases.id')) + file_database = relationship(FileDatabase) + + @classmethod + def new(cls, session, name, **kwargs): + project = cls(name=name, **kwargs) + client = FileDatabaseClient.new( + session=session, + root_directory=os.environ['FDB_ROOT_DIR'], + meta={'project_name': name}, + ) + project.file_database = client.instance + session.add(project) + return project +``` + +The `FDB_ROOT_DIR` environment variable is one convention for pointing all `FileDatabase`s at a shared root volume. Replace it with whatever fits your deployment. + +### Pattern B: per-collection attachment + +When a resource needs a single collection (no nested grouping), use `FileCollectionMixin` to attach `FileCollection`s without a top-level `FileDatabase`: + +```python +# myapp_adapter/resources/dataset.py +from tethysext.atcore.mixins.file_collection_mixin import FileCollectionMixin +from tethysext.atcore.models.app_users import Resource + + +class Dataset(Resource, FileCollectionMixin): + TYPE = 'dataset' + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +The mixin adds a `file_collections` relationship to `ResourceFileCollectionAssociation` and exposes `dataset.file_collection_client` once a collection is attached. + +### Controller-side: file upload/download + +To get upload/download views for a resource with collections, mix `FileCollectionsControllerMixin` into the matching `Manage*` controller: + +```python +# tethysapp/myapp/controllers/resources/manage_datasets.py +from tethysext.atcore.controllers.app_users import ManageResources +from tethysext.atcore.mixins.file_collection_controller_mixin import ( + FileCollectionsControllerMixin, +) + + +class ManageDatasets(ManageResources, FileCollectionsControllerMixin): + pass +``` + +It wires `_handle_delete` to drop the on-disk collection directory when the resource is deleted and provides the helpers [`ResourceFilesTab`](../api/controllers/resources/tabs/files_tab.mdx) uses to render the file listing. + +## See also + +- [Add a custom resource type](../how-to/add-a-resource-type.md) — combines `FileCollectionMixin` with a custom resource. +- [Wire up a file database](../how-to/wire-up-a-file-database.md) — end-to-end walkthrough. diff --git a/website/docs/concepts/gizmos.md b/website/docs/concepts/gizmos.md new file mode 100644 index 00000000..c0d4492c --- /dev/null +++ b/website/docs/concepts/gizmos.md @@ -0,0 +1,71 @@ +--- +id: concepts-gizmos +title: Gizmos +sidebar_label: Gizmos +sidebar_position: 7 +--- + +# Gizmos + +atcore exports two custom Tethys [gizmos](https://docs.tethysplatform.org/en/stable/tethys_sdk/gizmos.html) — small reusable UI widgets your templates can render with the standard `{% gizmo %}` tag. + +## SlideSheet + +[`SlideSheet`](../api/gizmos/slide_sheet.mdx#slidesheet) is a slide-out panel anchored to the right edge of the page. Use it to host secondary content — layer details, plots, forms — without leaving the current view. + +```python +# example — controller +from tethysext.atcore.gizmos import SlideSheet + +slide_sheet = SlideSheet( + id='layer-details', + content_template='my_first_app/partials/layer_details.html', + title='Layer Details', +) +context = {'slide_sheet': slide_sheet} +``` + +```html +{% load tethys_gizmos %} +{% gizmo slide_sheet %} +``` + +The widget loads its content from the template you point at via `content_template`. Open and close it from JavaScript using the id-suffixed handlers in `atcore/gizmos/slide_sheet/slide_sheet.js`. + +`MapView` already integrates a `SlideSheet` for layer / feature details — see [`MapView`](../api/controllers/map_view.mdx#mapview). + +## SpatialReferenceSelect + +[`SpatialReferenceSelect`](../api/gizmos/spatial_reference_select.mdx#spatialreferenceselect) is a `select2`-backed lookup input for picking an EPSG spatial reference system by SRID or name. + +```python +# example — controller +from tethysext.atcore.gizmos import SpatialReferenceSelect + +srs_select = SpatialReferenceSelect( + display_name='Coordinate System', + name='srs', + placeholder='Search by EPSG code or name...', + spatial_reference_service=reverse('my_first_app:atcore_query_spatial_reference'), + initial=('NAD83 / UTM zone 12N', '26912'), +) +``` + +The `spatial_reference_service` URL must point at the `atcore_query_spatial_reference` endpoint registered by [`tethysext.atcore.urls.spatial_reference.urls`](../api/urls/spatial_reference.mdx). Wire it once in your `app.py`: + +```python +# example — app.py register_url_maps +from tethysext.atcore.urls import spatial_reference as sr_urls + +url_maps = sr_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='primary_db', +) +``` + +The endpoint queries `spatial_ref_sys` via [`SpatialReferenceService`](../api/services/spatial_reference.mdx). + +:::note +Both gizmos register their own JS/CSS via `get_gizmo_js` / `get_gizmo_css` and (for `SpatialReferenceSelect`) require `select2` from `vendor_static_dependencies`. As long as the gizmo is rendered through `{% gizmo %}`, Tethys handles the asset wiring. +::: diff --git a/website/docs/concepts/overview.md b/website/docs/concepts/overview.md new file mode 100644 index 00000000..9c714fb0 --- /dev/null +++ b/website/docs/concepts/overview.md @@ -0,0 +1,50 @@ +--- +id: concepts-overview +title: Overview +sidebar_label: Overview +sidebar_position: 1 +--- + +# What atcore is (and isn't) + +`tethysext-atcore` is a Tethys Platform extension that provides reusable parts for the kind of data-heavy, organization-aware web apps Aquaveo tends to build. Drop it in alongside Tethys, then subclass and configure rather than rebuilding the same models, controllers, and permissions in every app. + +## What atcore gives you + +The package layout under [`tethysext/atcore/`](https://github.com/Aquaveo/tethysext-atcore/tree/master/tethysext/atcore) maps directly to the [API reference](../api/index.mdx): + +| Subpackage | Purpose | +| --- | --- | +| [`models`](../api/models/index.mdx) | SQLAlchemy models — `AppUser`, `Organization`, `Resource`, `ResourceWorkflow`, workflow steps and results, `FileDatabase`. | +| [`controllers`](../api/controllers/index.mdx) | Class-based Tethys controllers — `MapView`, `ResourceView`, app-user CRUD pages, workflow router, REST endpoints. | +| [`services`](../api/services/index.mdx) | Stateful helpers — permissions manager, spatial managers, model database manager, condor workflow managers, file database client. | +| [`urls`](../api/urls/index.mdx) | `urls(...)` factory functions that emit `UrlMap` tuples for the app-user, resource, workflow, and spatial-reference URL groups. | +| [`gizmos`](../api/gizmos/index.mdx) | Tethys gizmos: [`SlideSheet`](../api/gizmos/slide_sheet.mdx) and [`SpatialReferenceSelect`](../api/gizmos/spatial_reference_select.mdx). | +| [`mixins`](../api/mixins/index.mdx) | Behavior mixins that the models compose — status, attributes, options, results, user lock, serialize, file collection. | +| [`permissions`](../api/permissions/index.mdx) | The `PermissionsGenerator` that builds the role/license permission matrix. | +| [`forms`](../api/forms/index.mdx) | Custom form widgets. | +| [`exceptions`](../api/exceptions/index.mdx) | The `ATCoreException` family. | +| [`cli`](../api/cli/index.mdx) | The `atcore` console command (currently `atcore init`). | + +## How the pieces fit + +A typical atcore-backed page looks like this: + +1. A `urls(...)` helper from [`tethysext.atcore.urls`](../api/urls/index.mdx) registers a `UrlMap` that points at one of atcore's class-based controllers. +2. The controller (e.g. [`MapView`](../api/controllers/map_view.mdx) or [`ResourceWorkflowRouter`](../api/controllers/resource_workflows/resource_workflow_router.mdx)) is a `TethysController` subclass that uses a SQLAlchemy session against the app's app-users persistent store. +3. Decorators in [`services.app_users.decorators`](../api/services/app_users/decorators.mdx) wrap controller methods with auth checks (`active_user_required`, `resource_controller`). +4. The controller looks up an [`AppUser`](../api/models/app_users/app_user.mdx#appuser) and a [`Resource`](../api/models/app_users/resource.mdx#resource) for the request, and an [`AppPermissionsManager`](../api/services/app_users/permissions_manager.mdx#apppermissionsmanager) decides what the user can do. +5. For long-running work, a [`ResourceCondorWorkflow`](../api/services/resource_condor_workflow.mdx) or [`ResourceWorkflowCondorJobManager`](../api/services/workflow_manager/condor_workflow_manager.mdx) submits Condor jobs that update the resource's status when they finish. + +## What atcore is not + +- **Not a Tethys replacement.** You still build a normal Tethys app — atcore plugs into it. +- **Not a generic ORM.** The SQLAlchemy models are tailored to the app-user / organization / resource / workflow domain. Use them as-is or subclass; don't expect them to model arbitrary domains. +- **Not opinion-free.** atcore assumes Postgres + PostGIS, an app-users persistent store, GeoServer for spatial layers, and HTCondor for batch jobs. You can stub or replace pieces, but the defaults are concrete. + +## Where to go next + +- New to the project? Start with [Installation](../getting-started/installation.md). +- Planning an atcore-backed app's layout? Read [Project Structure](./project-structure.md) for the two-package adapter pattern. +- Building your first atcore page? Read [App Users](./app-users.md) and [Resources](./resources.md), then [Controllers](./controllers.md). +- Need the full class signatures? Jump to the [API Reference](../api/index.mdx). diff --git a/website/docs/concepts/permissions.md b/website/docs/concepts/permissions.md new file mode 100644 index 00000000..d7077eef --- /dev/null +++ b/website/docs/concepts/permissions.md @@ -0,0 +1,145 @@ +--- +id: concepts-permissions +title: Permissions +sidebar_label: Permissions +sidebar_position: 8 +--- + +# Permissions + +atcore generates a permission matrix from two axes — the user's [role](../api/services/app_users/roles.mdx) and the licenses of the [organizations](./app-users.md) they belong to. The result is a fixed set of permission groups that you wire into Tethys's permission system. + +## The matrix + +Roles ([`Roles`](../api/services/app_users/roles.mdx)): + +| Role | Description | +| --- | --- | +| `ORG_USER` | Member of an organization. | +| `ORG_REVIEWER` | Member with review authority. | +| `ORG_ADMIN` | Manages members and resources in their org. | +| `APP_ADMIN` | Manages everything in the app. | +| `DEVELOPER` | Out-ranks all roles (used for staff). | + +Licenses ([`Licenses`](../api/services/app_users/licenses.mdx)): + +| License | Notes | +| --- | --- | +| `STANDARD` | | +| `ADVANCED` | | +| `PROFESSIONAL` | | +| `CONSULTANT` | Can have client organizations. | + +The cross-product produces 12 organizational permission groups (license × {`USER`, `REVIEWER`, `ADMIN`}) plus the global `APP_A_PERMS`. Each group is namespaced to your app — e.g. `my_first_app:standard_admin_perms`. + +## AppPermissionsManager + +Use [`AppPermissionsManager`](../api/services/app_users/permissions_manager.mdx#apppermissionsmanager) to look up the permission group for a user. It's instantiated with your app namespace: + +```python +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager + +pm = AppPermissionsManager('my_first_app') +group = pm.get_permissions_group_for(role=Roles.ORG_ADMIN, license=Licenses.STANDARD) +# 'my_first_app:standard_admin_perms' +``` + +Common methods: + +- `list(with_namespace=False)` — all enabled groups. +- `get_permissions_group_for(role, license=...)` — group name for a (role, license) pair. +- `get_has_role_permission_for(role, license=...)` — name of the per-role flag permission. + +## Generating permissions + +Register the permission groups with Tethys by hooking [`PermissionsGenerator`](../api/permissions/app_users.mdx#permissionsgenerator) into your app's `permissions()` method: + +```python +# example — app.py +from tethys_sdk.base import TethysAppBase +from tethysext.atcore.permissions.app_users import PermissionsGenerator +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager + + +class MyFirstApp(TethysAppBase): + name = 'My First App' + + def permissions(self): + pm = AppPermissionsManager(self.namespace) + gen = PermissionsGenerator(pm) + # Optional: add app-specific permissions to a group + # gen.add_permissions_for(pm.STD_A_PERMS, [my_extra_permission]) + return gen.generate() +``` + +The generator emits Tethys `PermissionGroup` instances with a curated set of permissions per group. The full breakdown is in [`permissions/app_users.py`](https://github.com/Aquaveo/tethysext-atcore/blob/master/tethysext/atcore/permissions/app_users.py); a quick view is on the [Permissions cheat sheet](../reference/permissions-cheatsheet.md). + +### App-specific permissions: post-mutation + +`PermissionsGenerator.generate()` returns the standard permission groups. There's no extension hook for "add this permission to every admin group" or "every group with this suffix" — post-mutate the returned list: + +```python +from tethys_sdk.permissions import Permission + +def permissions(self): + pm = MyPermissionsManager(self.url_namespace) + groups = PermissionsGenerator(pm).generate() + + extra = Permission(name='set_map_extent', description='Edit project default extent.') + for group in groups: + if 'admin' in group.name: + group.permissions.append(extra) + + return groups +``` + +If your app has many app-specific permissions or a non-standard role/license set, subclass `PermissionsGenerator` and override `generate()` to define the full permission graph. Pick whichever approach matches the size of your customization. + +## Subclassing the permissions manager + +If you subclass `Roles` or `Licenses` to subset or extend the available values, subclass `AppPermissionsManager` too so it picks up your new sets: + +```python +# myapp_adapter/app_users/permissions.py +from tethysext.atcore.services.app_users.licenses import Licenses +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager + + +class MyLicenses(Licenses): + @classmethod + def list(cls): + return (cls.STANDARD, cls.CONSULTANT) + + +class MyPermissionsManager(AppPermissionsManager): + LICENSES = MyLicenses() +``` + +Pass it to the URL helpers via `custom_permissions_manager=MyPermissionsManager` so atcore's controllers use it for permission-group lookups. + +## Built-in permissions + +Permissions defined by `PermissionsGenerator` cluster into five buckets: + +- **Resource management** — `view_all_resources`, `view_resources`, `view_resource_details`, `create_resource`, `edit_resource`, `delete_resource`, `always_delete_resource`. +- **User management** — `view_users`, `view_all_users`, `modify_users`, `modify_user_manager`, plus role-assignment perms (`assign_org_user_role`, `assign_org_admin_role`, `assign_app_admin_role`, `assign_developer_role`, etc.). +- **Organization management** — `view_organizations`, `view_all_organizations`, `create_organizations`, `edit_organizations`, `delete_organizations`, `modify_organization_members`. +- **Assignment** — `assign_any_resource`, `assign_any_user`, `assign_any_organization`, plus license-assignment perms (`assign_standard_license`, `assign_advanced_license`, etc.). +- **Map view** — `remove_layers`, `rename_layers`, `toggle_public_layers`, `use_map_plot`, `use_map_geocode`, `can_download`, `can_export_datatable`. Plus `can_override_user_locks` for workflow lock overrides. + +## Checking permissions in code + +Use Tethys's standard permission helpers — atcore's groups are normal Tethys groups: + +```python +from tethys_sdk.permissions import has_permission, permission_required + +if has_permission(request, 'edit_resource'): + ... + +@permission_required('delete_resource') +def my_view(self, request, ...): + ... +``` + +For class-based controllers, atcore's `ResourceView` already runs the auth/active-user checks — apply `@permission_required` (or your own check) to the specific method or action. diff --git a/website/docs/concepts/project-structure.md b/website/docs/concepts/project-structure.md new file mode 100644 index 00000000..e34dba59 --- /dev/null +++ b/website/docs/concepts/project-structure.md @@ -0,0 +1,111 @@ +--- +id: concepts-project-structure +title: Project Structure +sidebar_label: Project structure +sidebar_position: 1.5 +--- + +# Project structure + +A small atcore-backed Tethys app can live entirely inside a single `tethysapp.<name>` package. Once the app grows past a few resources and a workflow or two, the usual move is to split it into two Python packages: + +- `tethysapp.<name>` — the Tethys app: `app.py`, controllers, templates, app-only services (e.g. the `MapManager`), and any wiring code that depends on Django or Tethys. +- `<name>-adapter` (sibling package, importable as `<name>_adapter`) — the domain core: SQLAlchemy models, `ResourceWorkflow` subclasses, custom `AppUser` / `Organization` / `Roles` / `Licenses`, the `SpatialManager`, and pure-Python services that should be testable without spinning up Tethys. + +Atcore doesn't enforce the split, but it's the convention. + +## Why split? + +1. Condor jobs and CLIs can import your models without dragging in Django. A Condor worker that calls back to update a workflow status shouldn't have to boot a Tethys app context. Models in the adapter package load from anything with a SQLAlchemy session. +2. Domain-layer tests stay fast. Workflow definitions, `Resource` subclasses, and permission generators have no Tethys dependency, so unit tests run in milliseconds and don't need a persistent store. +3. Reuse across apps. A second app that wants the same `Resource` types or workflow steps imports the adapter package directly. +4. The adapter package is what the app *is*; the Tethys package is how the app is rendered. + +## What goes where + +| Concern | `tethysapp.<name>` | `<name>-adapter` | +| --- | --- | --- | +| `app.py` (`TethysAppBase` subclass) | yes | — | +| URL maps, `register_url_maps()` | yes | — | +| Tethys controllers (`MapView`, `TabbedResourceDetails`, ...) | yes | — | +| HTML templates, static assets | yes | — | +| `MapManagerBase` subclass (renders to Tethys gizmos) | yes | — | +| `Resource` / `SpatialResource` subclasses | — | yes | +| `ResourceWorkflow` subclasses + step composition | — | yes | +| `AppUser` / `Organization` subclasses | — | yes | +| `Roles` / `Licenses` subclasses | — | yes | +| `BaseSpatialManager` subclass (talks to GeoServer + SQL) | — | yes | +| `AppPermissionsManager` / `PermissionsGenerator` subclasses | — | yes | +| Condor job scripts | — | yes | +| Alembic migrations | yes (next to `app.py`) | — | + +The Tethys package imports the adapter package when wiring URL maps and instantiating views. The adapter package never imports Tethys or Django. + +## Single-package layout (for small apps) + +If your app has one resource type and no workflows, keep everything in `tethysapp.<name>/`: + +``` +tethysapp-myapp/ +└── tethysapp/ + └── myapp/ + ├── app.py + ├── controllers/ + ├── models/ # Resource, Workflow subclasses live here + ├── services/ # MapManager, SpatialManager + └── templates/ +``` + +The walkthrough tutorial uses the single-package layout. Refactor into two packages when the trade-offs above start to bite. + +## Two-package layout + +``` +tethysapp-myapp/ +├── tethysapp-myapp/ # the Tethys app package +│ └── tethysapp/ +│ └── myapp/ +│ ├── app.py +│ ├── controllers/ +│ │ ├── resources/ # ManageMyResources, ModifyMyResource, ... +│ │ ├── workflows/ # MyWorkflowRouter +│ │ └── workflow_steps/ # custom step views (MapWorkflowView subclasses) +│ ├── services/ +│ │ └── map_manager.py # MyMapManager(MapManagerBase) +│ └── templates/myapp/ +└── myapp-adapter/ # the domain package + └── myapp_adapter/ + ├── app_users/ + │ ├── app_user.py # MyAppUser(AppUser) + │ ├── organization.py # MyOrganization(Organization) + │ └── permissions.py # Roles, Licenses, PermissionsGenerator + ├── resources/ + │ ├── project.py # Project(Resource) + │ ├── scenario.py # Scenario(Resource) + │ └── mixins/ # cross-cutting Resource mixins + ├── workflows/ + │ ├── base_workflow.py # MyWorkflow(ResourceWorkflow) — abstract + │ └── analysis/ # one package per workflow type + │ └── __init__.py # AnalysisWorkflow.new(...) + ├── workflow_steps/ # custom RWS subclasses + ├── services/ + │ └── spatial_manager.py # MySpatialManager(ResourceSpatialManager) + └── job_scripts/ # Condor worker entry points +``` + +Wire the two together by adding the adapter package to your `pyproject.toml` / `install.yml` and importing from it wherever you need a domain class: + +```python +# tethysapp/myapp/app.py +from myapp_adapter.resources.project import Project +from myapp_adapter.workflows.analysis import AnalysisWorkflow +from myapp_adapter.app_users.permissions import MyPermissionsGenerator +from .controllers.resources.manage_projects import ManageProjects, ModifyProject +from .controllers.workflows.my_workflow_router import MyWorkflowRouter +``` + +## Next + +- [App Users](./app-users.md) — what lives in the `app_users/` adapter subpackage. +- [Resources](./resources.md) — patterns for the `resources/` subpackage, including the mixin idiom. +- [Resource Workflows](./resource-workflows.md) — the `new()` factory used by every workflow in the `workflows/` subpackage. diff --git a/website/docs/concepts/resource-workflows.md b/website/docs/concepts/resource-workflows.md new file mode 100644 index 00000000..d7b00f2d --- /dev/null +++ b/website/docs/concepts/resource-workflows.md @@ -0,0 +1,247 @@ +--- +id: concepts-resource-workflows +title: Resource Workflows +sidebar_label: Resource Workflows +sidebar_position: 4 +--- + +# Resource workflows + +A `ResourceWorkflow` is a stateful, multi-step process attached to a `Resource`. Use it for wizard-style sequences (configure inputs, pick a region, submit a job, view results) with persistent intermediate state and authorization. + +## Pieces + +The data model lives in [`tethysext.atcore.models.app_users`](../api/models/app_users/index.mdx) and [`tethysext.atcore.models.resource_workflow_steps`](../api/models/resource_workflow_steps/index.mdx): + +- [`ResourceWorkflow`](../api/models/app_users/resource_workflow.mdx#resourceworkflow) — the parent record. References its `Resource` and `creator` (`AppUser`); owns ordered `steps` and `results`. +- [`ResourceWorkflowStep`](../api/models/app_users/resource_workflow_step.mdx) — a single step. Ordered, can reference other steps as parents, carries its own status. +- [`ResourceWorkflowResult`](../api/models/app_users/resource_workflow_result.mdx) — output produced by a `ResultsResourceWorkflowStep`. + +## Built-in step types + +Concrete subclasses of `ResourceWorkflowStep`: + +| Class | When to use it | +| --- | --- | +| [`SpatialInputRWS`](../api/models/resource_workflow_steps/spatial_input_rws.mdx) | Draw / edit features on a map. | +| [`SpatialAttributesRWS`](../api/models/resource_workflow_steps/spatial_attributes_rws.mdx) | Edit attributes of features added in a prior step. | +| [`SpatialDatasetRWS`](../api/models/resource_workflow_steps/spatial_dataset_rws.mdx) | Attach tabular datasets to spatial features. | +| [`SpatialCondorJobRWS`](../api/models/resource_workflow_steps/spatial_condor_job_rws.mdx) | Submit a spatially-aware Condor job. | +| [`FormInputRWS`](../api/models/resource_workflow_steps/form_input_rws.mdx) | Plain Django form input driven by a `param.Parameterized` class. | +| [`TableInputRWS`](../api/models/resource_workflow_steps/table_input_rws.mdx) | Tabular input (rows of typed cells). | +| [`XMSToolRWS`](../api/models/resource_workflow_steps/xms_tool_rws.mdx) | Run an XMS Tool against the workflow inputs. | +| [`SetStatusRWS`](../api/models/resource_workflow_steps/set_status_rws.mdx) | Manually transition the workflow status (e.g. submit-for-review). | +| [`ResultsResourceWorkflowStep`](../api/models/resource_workflow_steps/results_rws.mdx) | Display generated results. | + +## Built-in result types + +Found in [`tethysext.atcore.models.resource_workflow_results`](../api/models/resource_workflow_results/index.mdx): + +- [`SpatialWorkflowResult`](../api/models/resource_workflow_results/spatial_workflow_result.mdx) — map layers and features. +- [`DatasetWorkflowResult`](../api/models/resource_workflow_results/dataset_workflow_result.mdx) — tabular data. +- [`PlotWorkflowResult`](../api/models/resource_workflow_results/plot_workflow_result.mdx) — Plotly / Bokeh plots. +- [`ReportWorkflowResult`](../api/models/resource_workflow_results/report_workflow_result.mdx) — multi-section reports composed from the others. + +## The `new()` factory + +Every `ResourceWorkflow` subclass should define a `new()` classmethod with the same signature: + +```python +class AnalysisWorkflow(ResourceWorkflow): + TYPE = 'analysis' + DISPLAY_TYPE_SINGULAR = 'Analysis' + DISPLAY_TYPE_PLURAL = 'Analyses' + __mapper_args__ = {'polymorphic_identity': TYPE} + + @classmethod + def new(cls, app, name, resource_id, creator_id, + geoserver_name, map_manager, spatial_manager, **kwargs): + workflow = cls(name=name, resource_id=resource_id, creator_id=creator_id) + + step1 = SpatialInputRWS( + name='Pick study area', + order=1, + help='Draw or upload your area of interest.', + options={ + 'shapes': ['polygons', 'extents'], + 'singular_name': 'Area of Interest', + 'plural_name': 'Areas of Interest', + 'allow_shapefile': True, + 'allow_drawing': True, + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + + step2 = SpatialCondorJobRWS( + name='Run analysis', + order=2, + options={ + 'scheduler': app.SCHEDULER_NAME, + 'jobs': build_jobs_callback, + 'workflow_kwargs': {'max_jobs': {'analysis': 4}}, + 'working_message': 'Running analysis...', + 'error_message': 'Analysis failed.', + 'pending_message': 'Analysis pending.', + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + step2.parents.append(step1) + + step3 = ResultsResourceWorkflowStep(name='Results', order=3) + step2.result = step3 + + workflow.steps.extend([step1, step2, step3]) + return workflow +``` + +The URL helpers and atcore's controllers pass `app`, `geoserver_name`, `map_manager`, and `spatial_manager` through to your factory when constructing a new workflow. Match this signature and the rest of atcore drops in. + +### Why a factory and not just declared steps? + +Steps need runtime values — the spatial manager configured for the request, the scheduler name, app-level options — that aren't available at class-definition time. The factory takes those in, builds the step graph in memory, and returns the unsaved workflow. The caller commits. + +## Step options patterns + +The `options` dict on every step type is a mix of: + +1. Static config — `'shapes': ['polygons']`, `'singular_name': 'Basin'`, `'allow_shapefile': True`. +2. Callable values, for things that need to evaluate at form-render or job-submit time. Pass a function instead of a value: + ```python + options={ + 'jobs': build_jobs_callback, # SpatialCondorJobRWS + 'template_dataset': build_dataset_cb, # SpatialDatasetRWS + 'plot_columns': build_columns_cb, # SpatialDatasetRWS + } + ``` + The atcore step view calls these at the right moment with the live workflow context. +3. Dot-path strings for lazily-imported classes. `FormInputRWS.options['param_class']` and `XMSToolRWS.options['xmstool_class']` accept a string like `'myapp_adapter.workflows.analysis.options.AnalysisOptions'` and import it on demand. This dodges the circular import you'd otherwise hit when the form options module imports resource models that the workflow definition module also imports. +4. Inter-step dependency declarations. `SpatialDatasetRWS` reads from a parent step: + ```python + options={ + 'geometry_source': { + SpatialDatasetRWS.OPT_PARENT_STEP: { + 'match_attr': 'name', + 'match_value': 'Pick study area', + 'parent_field': 'geometry', + }, + }, + } + ``` + `OPT_PARENT_STEP` tells the step to pull geometry from a named parent step's output rather than re-asking the user. + +## Step authorization with `active_roles` + +Pass `active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN]` to each step constructor to gate which app-user roles can see and execute it. Use it to build review-style workflows where the first few steps are user-facing and the last step is admin-only: + +```python +review_step = SetStatusRWS( + name='Review', + order=4, + active_roles=[Roles.ORG_REVIEWER, Roles.APP_ADMIN], +) +``` + +Combine `active_roles` with the status state machine below to build multi-actor workflows. + +## Connecting steps to results + +A `SpatialCondorJobRWS` step wires its results page through the singular `result` attribute, not by appending to `workflow.results`: + +```python +condor_step.result = results_step +workflow.steps.extend([..., condor_step, results_step]) +``` + +The condor manager reads `condor_step.result` when building `SpatialWorkflowResult` / `DatasetWorkflowResult` rows after the job finishes. + +## Status progression + +`ResourceWorkflow` defines a primary status progression and an optional review track: + +``` +Primary: + PENDING -> CONTINUE -> WORKING -> COMPLETE + \-> ERROR / FAILED + +Review (optional): + SUBMITTED -> UNDER_REVIEW -> APPROVED / REJECTED / CHANGES_REQUESTED + -> REVIEWED +``` + +The status rolls up from the steps: `WORKING` if any step is processing, `ERROR` / `FAILED` if any step has errored, otherwise the lowest-severity status across steps. + +## Controllers + +The router is [`ResourceWorkflowRouter`](../api/controllers/resource_workflows/resource_workflow_router.mdx#resourceworkflowrouter). It dispatches each step to a view based on the step's `CONTROLLER` attribute — a dot-path string pointing at a `ResourceWorkflowView` subclass. The base view is [`ResourceWorkflowView`](../api/controllers/resource_workflows/workflow_view.mdx). Concrete views live in: + +- [`controllers.resource_workflows.workflow_views`](../api/controllers/resource_workflows/workflow_views/index.mdx) — non-spatial step views. +- [`controllers.resource_workflows.map_workflows`](../api/controllers/resource_workflows/map_workflows/index.mdx) — map-based step views. +- [`controllers.resource_workflows.results_views`](../api/controllers/resource_workflows/results_views/index.mdx) — result viewers. + +Most apps subclass `ResourceWorkflowRouter` only to override `default_back_url(request, resource_id)` so the "back" link returns to a sensible page (e.g., the resource's tabbed details). + +## Custom step types + +When the built-in step types don't fit, subclass an appropriate base step and bind it to a custom view via `CONTROLLER`: + +```python +# myapp_adapter/workflow_steps/ndvi_rws.py +from tethysext.atcore.models.app_users import SpatialResourceWorkflowStep + +class NDVIRWS(SpatialResourceWorkflowStep): + CONTROLLER = 'tethysapp.myapp.controllers.workflow_steps.ndvi_mwv.NDVIMWV' + TYPE = 'ndvi_step' + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +```python +# tethysapp/myapp/controllers/workflow_steps/ndvi_mwv.py +from tethysext.atcore.controllers.resource_workflows.map_workflows import MapWorkflowView +from myapp_adapter.workflow_steps.ndvi_rws import NDVIRWS + +class NDVIMWV(MapWorkflowView): + template_name = 'myapp/workflow_steps/ndvi.html' + valid_step_classes = [NDVIRWS] + + def process_step_data(self, request, session, step, ...): + ... +``` + +Both ends must agree: the router uses `CONTROLLER` to dispatch, and the view uses `valid_step_classes` to refuse to render against the wrong step type. + +See [Add a custom workflow step type](../how-to/add-a-custom-workflow-step-type.md) for the full recipe. + +## Wiring URLs + +Use [`tethysext.atcore.urls.resource_workflows.urls`](../api/urls/resource_workflows.mdx) and pass `workflow_pairs` — `(WorkflowModel, RouterClass)` tuples: + +```python +# example — app.py register_url_maps +from tethysext.atcore.urls import resource_workflows as rw_urls +from .controllers.workflows.my_router import MyWorkflowRouter +from myapp_adapter.workflows.analysis import AnalysisWorkflow + +url_maps = rw_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='primary_db', + workflow_pairs=((AnalysisWorkflow, MyWorkflowRouter),), + custom_models=(MyOrganization,), + custom_permissions_manager=MyPermissionsManager, + base_template='myapp/workflows_base.html', +) +``` + +Apps with several workflow types usually call `rw_urls.urls(...)` once per workflow class. `workflow_pairs` accepts a tuple, but the per-call options (`base_template`, `custom_models`, `custom_permissions_manager`) tend to vary per workflow surface, so one call per workflow is cleaner. + +## Behind the scenes + +The router decorates step controllers with [`workflow_step_controller`](../api/services/resource_workflows/decorators.mdx) from [`tethysext.atcore.services.resource_workflows.decorators`](../api/services/resource_workflows/decorators.mdx), which loads the workflow and step from URL kwargs into the view. + +For step types that submit Condor jobs, see [Run a Condor Workflow Job](../how-to/run-a-condor-workflow-job.md). diff --git a/website/docs/concepts/resources.md b/website/docs/concepts/resources.md new file mode 100644 index 00000000..b00ff209 --- /dev/null +++ b/website/docs/concepts/resources.md @@ -0,0 +1,185 @@ +--- +id: concepts-resources +title: Resources +sidebar_label: Resources +sidebar_position: 3 +--- + +# Resources + +A `Resource` is the central domain object in atcore. Subclass it once per "thing the user manages" (a project, scenario, model run, asset). Atcore's controllers and URL helpers do the CRUD wiring. + +## The base class + +[`Resource`](../api/models/app_users/resource.mdx#resource) is a SQLAlchemy model that composes four mixins: + +- [`StatusMixin`](../api/mixins/status_mixin.mdx) — keyed status dictionary (`get_status` / `set_status`), with constants like `STATUS_PENDING`, `STATUS_PROCESSING`, `STATUS_SUCCESS`, `STATUS_ERROR`. +- [`AttributesMixin`](../api/mixins/attributes_mixin.mdx) — free-form JSON attributes via `get_attribute` / `set_attribute`. +- [`UserLockMixin`](../api/mixins/user_lock_mixin.mdx) — soft lock so only one user can edit a resource at a time. +- [`SerializeMixin`](../api/mixins/serialize_mixin.mdx) — `serialize()` for dict / JSON responses. + +Columns include `id` (UUID), `name`, `description`, `type`, `date_created`, `created_by`, `status`, and `public`. Resources belong to one or more `Organization`s and can be nested via `parents` / `children`. + +## Subclassing + +Subclass `Resource` and set the polymorphic identity: + +```python +# example — myapp_adapter/resources/project.py +from tethysext.atcore.models.app_users import Resource + + +class Project(Resource): + TYPE = 'project' + DISPLAY_TYPE_SINGULAR = 'Project' + DISPLAY_TYPE_PLURAL = 'Projects' + + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +`Resource.SLUG` is a `classproperty` derived from `DISPLAY_TYPE_PLURAL`, so the example above produces URL maps prefixed with `projects_`. + +## SpatialResource vs. raw geometry columns + +If your resource has a single rectangular geographic extent, subclass [`SpatialResource`](../api/models/app_users/spatial_resource.mdx). It adds a PostGIS `extent` column and `set_extent` / `get_extent` helpers that accept WKT, GeoJSON, or a Python dict (with an SRID). + +For richer geometry needs — arbitrary polygon areas-of-interest, point gauge locations, multiple geometries — stay on plain `Resource` and add a geoalchemy2 `Geometry` column directly: + +```python +from geoalchemy2 import Geometry +from sqlalchemy import Column + +class Project(Resource): + TYPE = 'project' + __mapper_args__ = {'polymorphic_identity': TYPE} + + area_of_interest = Column(Geometry('POLYGON', 4326)) +``` + +`SpatialResource` is convenient when all you need is an extent for the resource list / map preview. Apps that JOIN against the geometry, store multiple geometries, or use non-rectangular shapes inherit from `Resource` directly and add their own column. + +## Hard columns vs. soft attributes + +The split: + +- Hard SQL columns for fields you filter, JOIN, or query against — geometry, foreign keys, anything the database needs to reason about. These go on the `Resource` subclass as `Column(...)`. +- Soft attributes via the inherited `_attributes` JSON blob for everything else — sparse per-resource configuration, status keys, file paths, runtime state. Use `set_attribute('foo', value)` and `get_attribute('foo', default)`. + +```python +# example — controller code that uses both styles +project.area_of_interest # hard column — JOIN-friendly +project.set_attribute('database_id', uuid.uuid4().hex) +project.set_attribute('input_files', ['boundary.shp', 'soils.tif']) + +db_id = project.get_attribute('database_id') +``` + +Things like `database_id`, `srid`, `input_file`, `dataset_type`, `extent_geometry` are typically attributes, not columns. The benefit: you can add new state to a resource without writing a migration. The cost: no `WHERE database_id = ?` in SQL. When that becomes a real need, promote the field to a column and write the migration. + +## Cross-cutting behavior with mixins + +When several `Resource` subclasses share behavior — a parent-child link, an SRID attribute, an input-file convention — don't fatten the base class. Write a mixin and compose it onto each subclass that needs it. Atcore itself uses this pattern (`StatusMixin`, `AttributesMixin`, `UserLockMixin`); apps extend it: + +```python +# example — myapp_adapter/resources/mixins/srid_attr_mixin.py +class SridAttrMixin: + """Adds an `srid` property backed by the AttributesMixin store.""" + SRID_KEY = 'srid' + + @property + def srid(self): + return self.get_attribute(self.SRID_KEY) + + @srid.setter + def srid(self, value): + self.set_attribute(self.SRID_KEY, int(value)) + + +# myapp_adapter/resources/scenario.py +from tethysext.atcore.models.app_users import Resource +from .mixins.srid_attr_mixin import SridAttrMixin + + +class Scenario(Resource, SridAttrMixin): + TYPE = 'scenario' + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +Mixins of this shape don't add columns; they expose typed accessors over the JSON `_attributes` blob. A mixin that does need a column (e.g., a `LinkMixin` using the `parents`/`children` association table) either declares the column directly or relies on inherited columns from `Resource`. + +## Lifecycle + +The default `Resource` lifecycle, as exercised by atcore controllers and the condor workflow helpers: + +1. Create — `ModifyResource` creates the row, runs validation, and sets `status` to `STATUS_PENDING`. +2. Initialize — long-running setup runs as a Condor job (see [`ResourceCondorWorkflow`](../api/services/resource_condor_workflow.mdx)). The job posts back to the resource's status using one or more status keys. +3. Available — once the initialization keys all resolve to an OK status (`STATUS_AVAILABLE`, `STATUS_SUCCESS`, etc.), the resource is usable. +4. Edit / use — `ResourceDetails`, `MapView`, and any custom workflows act on the resource. +5. Delete — `ModifyResource` flips `status` to `STATUS_DELETING` and removes the row plus any side effects. + +`StatusMixin` exposes `OK_STATUSES`, `ERROR_STATUSES`, `WORKING_STATUSES`, and `COMPLETE_STATUSES` for grouping. + +:::tip Async deletion for resources with heavy artifacts +Resources that own large file databases, GeoServer layers, or Condor working directories should override `ManageResources._handle_delete` to flip the status to `STATUS_DELETING` and spawn a daemon `Thread` for the cleanup. Synchronous deletion blocks the request and can time out on big projects. +::: + +## Wiring resource URLs + +Two equivalent ways to register CRUD pages for a resource subclass. + +Single resource, dedicated call — useful when the resource pages *are* the app: + +```python +# example — app.py register_url_maps +from tethysext.atcore.urls import resources as resources_urls +from myapp_adapter.resources.project import Project + +url_maps = list(resources_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='primary_db', + resource_model=Project, + base_template='myapp/base.html', +)) +``` + +Multiple resources, consolidated call — preferred when you have several resource types, because it produces the app-user pages and per-resource pages in one shot: + +```python +# example — app.py register_url_maps +from tethysext.atcore.urls import app_users as app_users_urls +from myapp_adapter.resources.project import Project +from myapp_adapter.resources.scenario import Scenario +from myapp.controllers.resources.manage_projects import ManageProjects, ModifyProject +from myapp.controllers.resources.manage_scenarios import ManageScenarios, ModifyScenario + +url_maps = list(app_users_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='primary_db', + custom_resources={ + Project: [ManageProjects, ModifyProject], + Scenario: [ManageScenarios, ModifyScenario], + }, + custom_models=[MyAppUser, MyOrganization], + custom_permissions_manager=MyPermissionsManager, + base_template='myapp/base.html', +)) +``` + +`custom_resources` accepts a dict of `{ResourceClass: [Manage, Modify(, Details)]}`. `app_users.urls(...)` calls `resources.urls(...)` once per entry, so app-user, organization, and resource URLs are registered together. + +Either way, the URLs produced (with `Project.SLUG` substituted): + +- `<slug>_manage_resources` +- `<slug>_new_resource` +- `<slug>_edit_resource` +- `<slug>_resource_details` +- `<slug>_resource_status` +- `<slug>_resource_status_list` + +## Next + +- [Resource Workflows](./resource-workflows.md) — multi-step processes attached to a resource. +- [Controllers](./controllers.md) — subclassing `ManageResources`, `ModifyResource`, and `TabbedResourceDetails`. +- [File Database](./file-database.md) — how a resource owns on-disk artifacts. diff --git a/website/docs/concepts/services.md b/website/docs/concepts/services.md new file mode 100644 index 00000000..fab3db04 --- /dev/null +++ b/website/docs/concepts/services.md @@ -0,0 +1,107 @@ +--- +id: concepts-services +title: Services +sidebar_label: Services +sidebar_position: 6 +--- + +# Services + +The `services` package holds atcore's stateful helpers — the bits that aren't models or controllers but back the behavior of both. + +## Spatial managers + +[`BaseSpatialManager`](../api/services/base_spatial_manager.mdx) is the abstract parent for managers that talk to GeoServer. Concrete managers: + +- [`ModelDBSpatialManager`](../api/services/model_db_spatial_manager.mdx) — for layers tied to a model database. +- [`ModelFileDBSpatialManager`](../api/services/model_file_db_spatial_manager.mdx) — for layers tied to a model + file database. +- [`ResourceSpatialManager`](../api/services/resource_spatial_manager.mdx) — for layers tied to a `Resource`. + +A spatial manager owns the GeoServer workspace name (`WORKSPACE`), URI, cluster ports, SLD path, and the SQL/PostGIS path. Subclass it and override what you need: + +```python +# example — services/my_spatial_manager.py +from tethysext.atcore.services.base_spatial_manager import BaseSpatialManager + + +class MySpatialManager(BaseSpatialManager): + WORKSPACE = 'my_first_app' + URI = 'http://app.aquaveo.com/my_first_app' +``` + +The `reload_config` decorator from [`services.base_spatial_manager`](../api/services/base_spatial_manager.mdx) refreshes the GeoServer cluster after mutating ops. + +## Map manager + +[`MapManagerBase`](../api/services/map_manager.mdx) builds Tethys `MapView` configurations for `MapView` controllers. Subclass it and implement the abstract `compose_map(self, request, *args, **kwargs)` to return a `(MapView, extent)` pair (where `extent` is a 4-list of floats). + +## Model database + +[`ModelDatabase`](../api/services/model_database.mdx) and [`ModelDatabaseConnection`](../api/services/model_database_connection.mdx) wrap a per-resource Postgres database. The base classes are [`ModelDatabaseBase`](../api/services/model_database_base.mdx) and [`ModelDatabaseConnectionBase`](../api/services/model_database_connection_base.mdx). The file-backed cousin is [`ModelFileDatabase`](../api/services/model_file_database.mdx) / [`ModelFileDatabaseConnection`](../api/services/model_file_database_connection.mdx). + +Use a `ModelDatabase` when each resource needs an isolated Postgres database (e.g., one DB per scenario / project). It load-balances across multiple Tethys persistent-store DB connections if your app declares more than one. + +### The connection-pool pattern + +`ModelDatabase` doesn't operate against a single named database. It creates one PostGIS database per resource, selecting from a pool of `PersistentStoreConnectionSetting` entries the app declares: + +```python +def persistent_store_settings(self): + return ( + PersistentStoreDatabaseSetting( + name='primary_db', initializer='myapp.models.init_primary_db', + spatial=True, required=True, + ), + PersistentStoreConnectionSetting(name='model_db_1', required=True), + PersistentStoreConnectionSetting(name='model_db_2', required=True), + PersistentStoreConnectionSetting(name='model_db_3', required=True), + ) +``` + +Each `model_db_N` is a *connection* to a Postgres server, not a database. When `ModelDatabase(app=app, database_id=...).initialize()` runs, atcore picks the least-loaded connection and creates a fresh database on it named after `database_id`. Resource-to-server assignment balances across the declared connections, so you scale by adding more `model_db_N` entries. + +Resources record their assigned `database_id` as an attribute (`resource.set_attribute('database_id', uuid_hex)`); the `MapManager` resolves it back to a `ModelDatabase` when rendering layers. + +### `ModelDatabase` vs. `FileDatabase` + +| | `ModelDatabase` | `FileDatabase` | +| --- | --- | --- | +| Storage | Per-resource PostGIS database | Per-resource directory on disk | +| Best for | Spatial layers published to GeoServer; SQL-queryable per-resource state | Bulk input/output files (rasters, archives, model output) | +| Layer publishing | `ModelDBSpatialManager` registers the DB as a GeoServer datastore | `BaseSpatialManager` reads files and publishes layers individually | +| Cleanup | Drop the database when the resource is deleted | Drop the directory tree | +| Scaling | Add more `PersistentStoreConnectionSetting` entries | Mount more disk | + +Many apps use both: `ModelDatabase` for the per-resource layer store, `FileDatabase` for the input rasters that fed it. + +## Permissions manager + +[`AppPermissionsManager`](../api/services/app_users/permissions_manager.mdx#apppermissionsmanager) is the runtime helper for atcore's role/license matrix. See the [Permissions concept page](./permissions.md) and the [Permissions cheat sheet](../reference/permissions-cheatsheet.md). + +## Workflow managers + +For long-running workflow steps, atcore submits HTCondor workflows: + +- [`BaseWorkflowManager`](../api/services/workflow_manager/base_workflow_manager.mdx) — base. +- [`ResourceWorkflowCondorJobManager`](../api/services/workflow_manager/condor_workflow_manager.mdx) — submits Condor workflows for a `ResourceWorkflowStep` and writes status back to the resource. +- [`ResourceCondorWorkflow`](../api/services/resource_condor_workflow.mdx) — submits the resource initialization workflow that runs after a `Resource` is created. + +See [Run a Condor Workflow Job](../how-to/run-a-condor-workflow-job.md) for an end-to-end example. + +## File database client + +[`FileDatabaseClient`](../api/services/file_database.mdx#filedatabaseclient) and [`FileCollectionClient`](../api/services/file_database.mdx#filecollectionclient) wrap the [`FileDatabase`](../api/models/file_database/file_database-module.mdx) model. See [File Database](./file-database.md). + +## Spatial reference + +[`SpatialReferenceService`](../api/services/spatial_reference.mdx) queries the PostGIS `spatial_ref_sys` table by SRID or name. It backs the `QuerySpatialReference` REST controller and the [`SpatialReferenceSelect`](./gizmos.md) gizmo. + +## Pagination and color ramps + +- [`paginate`](../api/services/paginate.mdx) — slice a list of records into pages with metadata. +- [`color_ramps`](../api/services/color_ramps.mdx) — predefined color ramps for thematic map styling. + +## Resource workflow helpers + +- [`services.resource_workflows.decorators.workflow_step_controller`](../api/services/resource_workflows/decorators.mdx) — view decorator for workflow step controllers. +- [`services.resource_workflows.helpers`](../api/services/resource_workflows/helpers.mdx) — shared helpers used by workflow views. diff --git a/website/docs/getting-started/_category_.json b/website/docs/getting-started/_category_.json new file mode 100644 index 00000000..3562d433 --- /dev/null +++ b/website/docs/getting-started/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Getting Started", + "position": 1 +} diff --git a/website/docs/getting-started/configuration.md b/website/docs/getting-started/configuration.md new file mode 100644 index 00000000..c0b1d217 --- /dev/null +++ b/website/docs/getting-started/configuration.md @@ -0,0 +1,148 @@ +--- +id: getting-started-configuration +title: Configuration +sidebar_label: Configuration +sidebar_position: 2 +--- + +# Configuration + +After [installing](./installation.md) atcore, you'll configure your Tethys portal and your app to use it. + +## Tethys portal `settings.py` + +Add the third-party Django apps that atcore depends on to `INSTALLED_APPS` in your portal's `settings.py` (e.g., `tethys/tethys_portal/settings.py`): + +```python +INSTALLED_APPS += [ + 'datetimewidget', + 'django_select2', + 'taggit', +] +``` + +Source: the project [README](https://github.com/Aquaveo/tethysext-atcore/blob/master/README.md#settingspy). + +## Persistent stores + +Your app needs at least one persistent store backed by PostgreSQL with the PostGIS extension. atcore's models live in this store. From your app's `app.py`: + +```python +# example — app.py +from tethys_sdk.app_settings import PersistentStoreDatabaseSetting + + +class MyFirstApp(TethysAppBase): + name = 'My First App' + package = 'my_first_app' + namespace = 'my_first_app' + + def persistent_store_settings(self): + return ( + PersistentStoreDatabaseSetting( + name='app_users_db', + description='Stores AppUsers, Organizations, Resources, Workflows.', + initializer='my_first_app.app.init_app_users_db', + spatial=True, + required=True, + ), + ) +``` + +`spatial=True` enables PostGIS, which `SpatialResource` and any GeoAlchemy2 model relies on. + +## Initializer + +The persistent-store initializer creates the atcore tables and seeds the staff/developer user: + +```python +# example — app.py +from tethysext.atcore.models.app_users import initialize_app_users_db + + +def init_app_users_db(engine, first_time): + initialize_app_users_db(engine, first_time=first_time) +``` + +If you've subclassed `AppUser`, pass it: + +```python +from .models.users import MyAppUser + +def init_app_users_db(engine, first_time): + initialize_app_users_db(engine, first_time=first_time, app_user_model=MyAppUser) +``` + +Run the initializer with `tethys syncstores my_first_app`. + +## Permissions + +atcore's `PermissionsGenerator` produces the role/license permission groups. Wire it into your app's `permissions()` method: + +```python +# example — app.py +from tethysext.atcore.permissions.app_users import PermissionsGenerator +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager + + +def permissions(self): + pm = AppPermissionsManager(self.namespace) + gen = PermissionsGenerator(pm) + return gen.generate() +``` + +See [Permissions](../concepts/permissions.md) for what gets generated. + +## URL maps + +Each atcore subsystem ships a `urls(...)` factory under [`tethysext.atcore.urls`](../api/urls/index.mdx). Compose them in `register_url_maps`: + +```python +# example — app.py +from tethysext.atcore.urls import ( + app_users as app_users_urls, + resources as resources_urls, + spatial_reference as sr_urls, +) + + +def register_url_maps(self): + UrlMap = url_map_maker(self.root_url) + url_maps = [] + + url_maps += list(app_users_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + base_template='my_first_app/base.html', + )) + url_maps += list(resources_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + resource_model=Project, # your Resource subclass + base_template='my_first_app/base.html', + )) + url_maps += list(sr_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + )) + + return tuple(url_maps) +``` + +## Test database (for running atcore's own tests) + +If you intend to run atcore's test suite, set the `ATCORE_TEST_DATABASE` environment variable to a PostgreSQL connection string for an empty test database: + +```bash +export ATCORE_TEST_DATABASE="postgresql://tethys_super:pass@172.17.0.1:5435/atcore_tests" +``` + +Default per the project README: + +```text +postgresql://tethys_super:pass@172.17.0.1:5435/atcore_tests +``` + +## Next + +Build a minimum-viable atcore app: [Your First atcore App](./first-app.md). diff --git a/website/docs/getting-started/first-app.md b/website/docs/getting-started/first-app.md new file mode 100644 index 00000000..52664d4b --- /dev/null +++ b/website/docs/getting-started/first-app.md @@ -0,0 +1,148 @@ +--- +id: getting-started-first-app +title: Your First atcore App +sidebar_label: Your first atcore app +sidebar_position: 3 +--- + +# Your first atcore app + +This page glues the [installation](./installation.md) and [configuration](./configuration.md) pages into the smallest possible Tethys app that uses atcore. The result: app-user / organization / resource management pages working out of the box. + +## Project layout + +``` +my_first_app/ +├── my_first_app/ +│ ├── __init__.py +│ ├── app.py +│ ├── controllers.py +│ ├── models/ +│ │ ├── __init__.py +│ │ └── projects.py +│ └── templates/ +│ └── my_first_app/ +│ └── base.html +├── install.yml +└── setup.py / pyproject.toml +``` + +## Define a `Resource` subclass + +```python +# my_first_app/models/projects.py +from sqlalchemy import Column, String +from tethysext.atcore.models.app_users import Resource + + +class Project(Resource): + TYPE = 'project' + DISPLAY_TYPE_SINGULAR = 'Project' + DISPLAY_TYPE_PLURAL = 'Projects' + + region = Column(String) + + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +## Wire `app.py` + +```python +# my_first_app/app.py +from tethys_sdk.base import TethysAppBase, url_map_maker +from tethys_sdk.app_settings import PersistentStoreDatabaseSetting +from tethysext.atcore.models.app_users import initialize_app_users_db +from tethysext.atcore.permissions.app_users import PermissionsGenerator +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager +from tethysext.atcore.urls import ( + app_users as app_users_urls, + spatial_reference as sr_urls, +) +from .models.projects import Project + + +def init_app_users_db(engine, first_time): + initialize_app_users_db(engine, first_time=first_time) + + +class MyFirstApp(TethysAppBase): + name = 'My First App' + package = 'my_first_app' + namespace = 'my_first_app' + index = 'home' + icon = f'{package}/images/icon.gif' + root_url = 'my-first-app' + color = '#3498db' + + def persistent_store_settings(self): + return ( + PersistentStoreDatabaseSetting( + name='app_users_db', + description='atcore database', + initializer='my_first_app.app.init_app_users_db', + spatial=True, + required=True, + ), + ) + + def permissions(self): + pm = AppPermissionsManager(self.namespace) + return PermissionsGenerator(pm).generate() + + def register_url_maps(self): + UrlMap = url_map_maker(self.root_url) + + url_maps = [ + UrlMap( + name='home', + url='my-first-app', + controller='my_first_app.controllers.home', + ), + ] + + # app_users.urls(custom_resources={...}) registers the user / + # organization pages and the per-resource CRUD pages in one call. + # Pass each Resource subclass mapped to its [Manage, Modify(, Details)] + # controllers, or [] to use the atcore defaults. + url_maps += list(app_users_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + custom_resources={Project: []}, # atcore defaults; pass [Manage, Modify] to override + base_template='my_first_app/base.html', + )) + + url_maps += list(sr_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + )) + + return tuple(url_maps) +``` + +:::tip Single-resource shortcut +If you only have one resource type and don't need the user/organization pages, call `resources_urls.urls(..., resource_model=Project)` directly. Prefer `app_users_urls.urls(custom_resources={...})` once you have more than one resource type or an `Organization` subclass — you won't have to rewire later. +::: + +## Bootstrap the database + +```bash +tethys syncstores my_first_app +``` + +This calls your initializer, which calls [`initialize_app_users_db`](../api/models/app_users/initializer.mdx), which creates the atcore tables and seeds the staff/developer user. + +## Try it out + +Start Tethys and visit: + +- `/apps/my-first-app/users/` — `ManageUsers` page (named `app_users_manage_users`). +- `/apps/my-first-app/organizations/` — `ManageOrganizations`. +- `/apps/my-first-app/projects/` — `ManageResources` for `Project`. + +The exact paths come from your `root_url` plus the URL prefix each atcore `urls(...)` factory adds. + +## Next + +- Add a custom workflow: [Build a Resource Workflow](../how-to/build-a-resource-workflow.md). +- Add a map page for your resource: [Customize a Map View](../how-to/customize-a-map-view.md). +- Read the end-to-end [walkthrough](../tutorials/walkthrough.md). diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md new file mode 100644 index 00000000..0cb9f83e --- /dev/null +++ b/website/docs/getting-started/installation.md @@ -0,0 +1,89 @@ +--- +id: getting-started-installation +title: Installation +sidebar_label: Installation +sidebar_position: 1 +--- + +# Installation + +`tethysext-atcore` is a [Tethys Platform](https://docs.tethysplatform.org) extension. You install it into an existing Tethys environment. + +## Requirements + +From [`install.yml`](https://github.com/Aquaveo/tethysext-atcore/blob/master/install.yml) and [`pyproject.toml`](https://github.com/Aquaveo/tethysext-atcore/blob/master/pyproject.toml): + +- **Tethys Platform** — the docker base image pins **Tethys 4.5.1**. Older 4.x versions may work but are not tested. +- **Python 3** — declared in classifiers. +- **PostgreSQL** with **PostGIS** for the app-users persistent store and any spatial models. +- **GeoServer** for spatial layer publishing (used by the spatial managers). +- **HTCondor** for batch / long-running jobs (used by the workflow managers). + +Conda dependencies (auto-installed via `tethys install`): + +```text +django>=3.2,<6 +django-select2<8.3.0 +django-taggit +geojson, jinja2, pandas, panel, param +pyshp>=3.0.0, requests +sqlalchemy<2 +``` + +Pip dependencies: + +```text +django-datetime-widget2 +geoserver-restconfig>=2.0.10 +``` + +## OS dependencies + +On Debian / Ubuntu: + +```bash +sudo apt update +sudo apt install gcc libgdal-dev g++ libhdf5-dev +``` + +These are required for building the spatial Python deps. + +## Activate the Tethys environment + +```bash +conda activate tethys +``` + +## Install the extension + +Clone the repo and run `tethys install` from the project root: + +```bash +git clone https://github.com/Aquaveo/tethysext-atcore.git +cd tethysext-atcore +tethys install -d # development (editable install) +# OR +tethys install # production +``` + +`tethys install` reads [`install.yml`](https://github.com/Aquaveo/tethysext-atcore/blob/master/install.yml) and resolves both conda and pip dependencies. + +## Initialize the GeoServer workspace + +atcore ships an `atcore init` console command that creates a default GeoServer workspace and uploads the bundled SLD styles: + +```bash +atcore init --gsurl http://admin:geoserver@localhost:8181/geoserver/rest/ +``` + +`--gsurl` defaults to `http://admin:geoserver@localhost:8181/geoserver/rest/`. The command is implemented in [`tethysext.atcore.cli.init_command`](../api/cli/init_command.mdx). + +## Verify the install + +```python +import tethysext.atcore # noqa +from tethysext.atcore.models.app_users import AppUser, Resource # noqa +from tethysext.atcore.controllers.map_view import MapView # noqa +``` + +If those imports succeed, you're ready to wire atcore into a Tethys app — see [Configuration](./configuration.md) and [Your First atcore App](./first-app.md). diff --git a/website/docs/how-to/_category_.json b/website/docs/how-to/_category_.json new file mode 100644 index 00000000..5c96369a --- /dev/null +++ b/website/docs/how-to/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "How-To Guides", + "position": 3 +} diff --git a/website/docs/how-to/add-a-custom-workflow-step-type.md b/website/docs/how-to/add-a-custom-workflow-step-type.md new file mode 100644 index 00000000..44463896 --- /dev/null +++ b/website/docs/how-to/add-a-custom-workflow-step-type.md @@ -0,0 +1,162 @@ +--- +id: how-to-add-a-custom-workflow-step-type +title: Add a Custom Workflow Step Type +sidebar_label: Add a custom workflow step type +sidebar_position: 8 +--- + +# Add a custom workflow step type + +The built-in step types ([`SpatialInputRWS`](../api/models/resource_workflow_steps/spatial_input_rws.mdx), [`FormInputRWS`](../api/models/resource_workflow_steps/form_input_rws.mdx), [`SpatialCondorJobRWS`](../api/models/resource_workflow_steps/spatial_condor_job_rws.mdx), [`XMSToolRWS`](../api/models/resource_workflow_steps/xms_tool_rws.mdx), ...) cover most needs. When you need something they don't — a domain-specific computation, a custom QC check, a specialized chart — define your own step type and a matching view. + +The example below is a "compute NDVI for the picked area" step. + +## 1. Subclass a step base + +Pick the closest existing base: [`SpatialResourceWorkflowStep`](../api/models/app_users/resource_workflow_step.mdx) for map-based interaction, `FormInputRWS` for plain forms, `SpatialCondorJobRWS` for Condor-backed work. Set `CONTROLLER` to the dot-path of the view that renders the step: + +```python +# myapp_adapter/workflow_steps/ndvi_rws.py +from tethysext.atcore.models.app_users import SpatialResourceWorkflowStep + + +class NDVIRWS(SpatialResourceWorkflowStep): + CONTROLLER = 'tethysapp.myapp.controllers.workflow_steps.ndvi_mwv.NDVIMWV' + TYPE = 'ndvi_step' + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +`CONTROLLER` is a string, not an import — atcore resolves it at request time. This dodges a circular import: the view in the Tethys package imports the step, and the step in the adapter package would otherwise need to import the view. + +If the step has its own attribute schema, add accessors via `set_attribute` / `get_attribute`: + +```python +class NDVIRWS(SpatialResourceWorkflowStep): + CONTROLLER = 'tethysapp.myapp.controllers.workflow_steps.ndvi_mwv.NDVIMWV' + TYPE = 'ndvi_step' + __mapper_args__ = {'polymorphic_identity': TYPE} + + @property + def red_band(self): + return self.get_attribute('red_band', 'B04') + + @red_band.setter + def red_band(self, value): + self.set_attribute('red_band', value) +``` + +## 2. Subclass a workflow view + +Pick the matching view base: [`MapWorkflowView`](../api/controllers/resource_workflows/map_workflows/index.mdx) for spatial steps, [`ResourceWorkflowView`](../api/controllers/resource_workflows/workflow_view.mdx) for non-spatial. Set `valid_step_classes` to the step types this view will render: + +```python +# tethysapp/myapp/controllers/workflow_steps/ndvi_mwv.py +from tethys_sdk.gizmos import MapView, MVLayer +from tethysext.atcore.controllers.resource_workflows.map_workflows import MapWorkflowView +from myapp_adapter.workflow_steps.ndvi_rws import NDVIRWS + + +class NDVIMWV(MapWorkflowView): + template_name = 'myapp/workflow_steps/ndvi.html' + valid_step_classes = [NDVIRWS] + + def get_context(self, request, session, resource, context, *args, **kwargs): + context = super().get_context(request, session, resource, context, *args, **kwargs) + # Anything the template needs beyond the base view. + context['available_bands'] = ['B02', 'B03', 'B04', 'B08'] + return context + + def process_step_data(self, request, session, step, *args, **kwargs): + # Read POSTed form data, validate, persist via step attributes. + red = request.POST.get('red_band') + if red: + step.red_band = red + # Returning super() lets atcore run its status transitions. + return super().process_step_data(request, session, step, *args, **kwargs) +``` + +`valid_step_classes` is the safety check: if the router dispatches the wrong step type, the view raises instead of silently rendering against the wrong schema. The router dispatches by `CONTROLLER`; the view refuses by `valid_step_classes`. Keep the two in sync. + +## 3. Provide the template + +The custom view needs a template that extends the right atcore template. For a map-based step: + +```html +{# tethysapp/myapp/templates/myapp/workflow_steps/ndvi.html #} +{% extends 'atcore/resource_workflows/spatial_workflow_view.html' %} + +{% block step_form_inputs %} + <div class="mb-3"> + <label class="form-label">Red band</label> + <select name="red_band" class="form-select"> + {% for band in available_bands %} + <option value="{{ band }}" {% if band == step.red_band %}selected{% endif %}> + {{ band }} + </option> + {% endfor %} + </select> + </div> +{% endblock %} +``` + +The atcore template handles the surrounding map, the layer toggle, and next/back navigation. Supply the form fields for your step. + +## 4. Use the step in a workflow + +Drop the step into a `ResourceWorkflow.new()` factory like any built-in step: + +```python +# myapp_adapter/workflows/vegetation/__init__.py +from tethysext.atcore.models.app_users import ResourceWorkflow +from tethysext.atcore.services.app_users.roles import Roles +from myapp_adapter.workflow_steps.ndvi_rws import NDVIRWS + + +class VegetationWorkflow(ResourceWorkflow): + TYPE = 'vegetation' + DISPLAY_TYPE_SINGULAR = 'Vegetation Analysis' + DISPLAY_TYPE_PLURAL = 'Vegetation Analyses' + __mapper_args__ = {'polymorphic_identity': TYPE} + + @classmethod + def new(cls, app, name, resource_id, creator_id, + geoserver_name, map_manager, spatial_manager, **kwargs): + wf = cls(name=name, resource_id=resource_id, creator_id=creator_id) + ndvi = NDVIRWS( + name='Compute NDVI', + order=1, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + wf.steps.append(ndvi) + return wf +``` + +The router reads `NDVIRWS.CONTROLLER`, resolves it to `NDVIMWV`, and renders. No router subclass needed. + +## 5. (Optional) Override the router only for navigation + +To send the "back" link somewhere specific, subclass `ResourceWorkflowRouter` and override `default_back_url`: + +```python +# tethysapp/myapp/controllers/workflows/my_router.py +from django.urls import reverse +from tethysext.atcore.controllers.resource_workflows import ResourceWorkflowRouter + + +class MyWorkflowRouter(ResourceWorkflowRouter): + def default_back_url(self, request, resource_id, *args, **kwargs): + return reverse('myapp:project_details_tab', kwargs={ + 'resource_id': resource_id, + 'tab_slug': 'workflows', + }) +``` + +Pair it with the workflow class in `rw_urls.urls(workflow_pairs=((VegetationWorkflow, MyWorkflowRouter),))`. + +## See also + +- [Build a Resource Workflow](./build-a-resource-workflow.md) for the surrounding workflow recipe. +- [Resource Workflows concept](../concepts/resource-workflows.md#custom-step-types). diff --git a/website/docs/how-to/add-a-resource-type.md b/website/docs/how-to/add-a-resource-type.md new file mode 100644 index 00000000..d49c938a --- /dev/null +++ b/website/docs/how-to/add-a-resource-type.md @@ -0,0 +1,104 @@ +--- +id: how-to-add-a-resource-type +title: Add a Resource Type +sidebar_label: Add a resource type +sidebar_position: 1 +--- + +# Add a resource type + +This recipe walks through subclassing `Resource` for a custom domain type and wiring its CRUD pages. + +## 1. Subclass `Resource` + +```python +# my_first_app/models/projects.py +from sqlalchemy import Column, String +from tethysext.atcore.models.app_users import Resource + + +class Project(Resource): + TYPE = 'project' + DISPLAY_TYPE_SINGULAR = 'Project' + DISPLAY_TYPE_PLURAL = 'Projects' + + region = Column(String) + + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +`Resource.SLUG` (a `classproperty`) auto-derives `'projects'` from `DISPLAY_TYPE_PLURAL`. URL maps for this resource will be prefixed `projects_`. + +If you need a geographic extent, subclass [`SpatialResource`](../api/models/app_users/spatial_resource.mdx) instead and set the polymorphic identity to your own type. + +## 2. Make sure the table is created + +The `Project` row lives in the same `app_users_resources` table as `Resource` (single-table inheritance). Calling [`initialize_app_users_db`](../api/models/app_users/initializer.mdx) is enough — no migration required if you add columns via single-table inheritance, since `region` becomes a nullable column on the shared table. + +If you add columns that must be present, run a one-off migration that adds the column. + +## 3. Register the resource URLs + +```python +# my_first_app/app.py — register_url_maps +from tethys_sdk.base import TethysAppBase, url_map_maker +from tethysext.atcore.urls import resources as resources_urls +from .models.projects import Project + + +class MyFirstApp(TethysAppBase): + name = 'My First App' + package = 'my_first_app' + namespace = 'my_first_app' + + def register_url_maps(self): + UrlMap = url_map_maker(self.root_url) + url_maps = [...] + + url_maps += list(resources_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='app_users_db', + resource_model=Project, + base_template='my_first_app/base.html', + )) + + return tuple(url_maps) +``` + +You'll get six URL maps: + +- `projects_manage_resources` +- `projects_new_resource` +- `projects_edit_resource` +- `projects_resource_details` +- `projects_resource_status` +- `projects_resource_status_list` + +## 4. (Optional) Override controllers + +If you need different behavior on the management pages, subclass any of [`ManageResources`](../api/controllers/app_users/manage_resources.mdx), [`ModifyResource`](../api/controllers/app_users/modify_resource.mdx), [`ResourceDetails`](../api/controllers/app_users/resource_details.mdx), or [`ResourceStatus`](../api/controllers/app_users/resource_status.mdx) and pass them via `custom_controllers`: + +```python +url_maps += list(resources_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='app_users_db', + resource_model=Project, + custom_controllers=[ProjectModifyResource, ProjectResourceDetails], +)) +``` + +The factory checks each entry against `ManageResources`, `ModifyResource`, `ResourceDetails`, and `ResourceStatus` and substitutes the right one. + +## 5. Link to the page + +```html +<a href="{% url 'my_first_app:projects_manage_resources' %}">My Projects</a> +``` + +The URL name is namespaced to your app's `namespace` setting. + +:::tip +Need attributes that vary per project but don't deserve a column? Use `project.set_attribute('foo', value)` (`AttributesMixin`) instead of adding a column. Stored as JSON in `_attributes`. +::: diff --git a/website/docs/how-to/add-a-rest-endpoint.md b/website/docs/how-to/add-a-rest-endpoint.md new file mode 100644 index 00000000..1ddfd514 --- /dev/null +++ b/website/docs/how-to/add-a-rest-endpoint.md @@ -0,0 +1,77 @@ +--- +id: how-to-add-a-rest-endpoint +title: Add a REST Endpoint +sidebar_label: Add a REST endpoint +sidebar_position: 4 +--- + +# Add a REST endpoint + +atcore exposes one REST controller out of the box — [`QuerySpatialReference`](../api/controllers/rest/spatial_reference.mdx). To add your own, follow the same shape: a class that inherits from `TethysController`, returns `JsonResponse`, and is wired through a `UrlMap`. + +## 1. Use the `resource_controller` decorator for resource-aware REST + +If your endpoint operates on a `Resource`, decorate the method with [`resource_controller(is_rest_controller=True)`](../api/services/app_users/decorators.mdx). The flag tells atcore to return `JsonResponse({'success': False, 'error': ...})` on errors instead of a redirect. + +```python +# my_first_app/controllers/rest/projects.py +from django.http import JsonResponse +from tethys_sdk.base import TethysController +from tethysext.atcore.services.app_users.decorators import ( + active_user_required, resource_controller, +) +from tethysext.atcore.controllers.app_users.mixins import ResourceViewMixin + + +class ProjectStatus(ResourceViewMixin, TethysController): + + @active_user_required() + @resource_controller(is_rest_controller=True) + def get(self, request, session, resource, back_url, *args, **kwargs): + return JsonResponse({ + 'success': True, + 'status': resource.get_status(), + 'name': resource.name, + }) +``` + +`ResourceViewMixin` provides `get_resource_model()` and `get_sessionmaker()`, both used by `resource_controller` to load the resource. + +## 2. Register the URL map + +```python +# my_first_app/app.py — register_url_maps +from .controllers.rest.projects import ProjectStatus + +UrlMap( + name='rest_project_status', + url='rest/projects/{resource_id}/status', + controller=ProjectStatus.as_controller( + _app=self, + _persistent_store_name='app_users_db', + _Resource=Project, + ), +) +``` + +`as_controller` needs the same kwargs the management controllers do — at minimum `_app`, `_persistent_store_name`, and `_Resource`. + +## 3. Use the `SpatialReferenceService` REST endpoint + +If you only need EPSG lookups for the [`SpatialReferenceSelect`](../concepts/gizmos.md) gizmo, register it via [`tethysext.atcore.urls.spatial_reference.urls`](../api/urls/spatial_reference.mdx) — the controller is `QuerySpatialReference`, and you'd point your gizmo's `spatial_reference_service` URL at the `atcore_query_spatial_reference` URL name. + +```python +# my_first_app/app.py — register_url_maps +from tethysext.atcore.urls import spatial_reference as sr_urls + +url_maps += list(sr_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='primary_db', +)) +``` + +## See also + +- [`active_user_required`](../api/services/app_users/decorators.mdx) — auth helper. +- [`resource_controller`](../api/services/app_users/decorators.mdx) — error handling and resource lookup. diff --git a/website/docs/how-to/add-a-tabbed-resource-details-page.md b/website/docs/how-to/add-a-tabbed-resource-details-page.md new file mode 100644 index 00000000..6737f5ed --- /dev/null +++ b/website/docs/how-to/add-a-tabbed-resource-details-page.md @@ -0,0 +1,150 @@ +--- +id: how-to-add-a-tabbed-resource-details-page +title: Add a Tabbed Resource Details Page +sidebar_label: Add a tabbed resource details page +sidebar_position: 7 +--- + +# Add a tabbed resource details page + +The default `ResourceDetails` controller shows a single page per resource. When that outgrows the form, switch to [`TabbedResourceDetails`](../api/controllers/resources/tabbed_resource_details.mdx). It composes tab classes from [`controllers.resources.tabs`](../api/controllers/resources/tabs/index.mdx) into a tabbed view and works with the existing `ManageResources` / `ModifyResource` URL maps. + +## 1. Choose your tabs + +atcore ships these tab classes: + +- [`ResourceSummaryTab`](../api/controllers/resources/tabs/summary_tab.mdx) — header card; subclass to add summary columns. +- [`ResourceFilesTab`](../api/controllers/resources/tabs/files_tab.mdx) — `FileCollection` listing. +- [`ResourceWorkflowsTab`](../api/controllers/resources/tabs/workflows_tab.mdx) — `ResourceWorkflow` listing plus new-workflow launcher. +- [`ResourceListTab`](../api/controllers/resources/tabs/resource_list_tab.mdx) — child resources. + +Mix built-ins with your own subclasses. + +## 2. Subclass `ResourceSummaryTab` for the summary card + +```python +# myapp/controllers/resources/tabs/project_summary_tab.py +from tethysext.atcore.controllers.resources import ResourceSummaryTab + + +class ProjectSummaryTab(ResourceSummaryTab): + def get_summary_tab_info(self, request, session, resource): + return { + 'general': { + 'title': 'General', + 'columns': [ + [('Region', resource.get_attribute('region') or '-')], + [('Inputs', resource.get_attribute('input_count') or 0)], + [('Status', resource.get_status('init') or 'unknown')], + ], + }, + } +``` + +`get_summary_tab_info` returns a dict of cards. Each card has `title` and `columns` (a list of column-lists of `(label, value)` tuples). + +## 3. Customize the workflows tab (optional) + +When the available workflow types depend on resource state (parents offer different workflows than leaves, etc.), subclass `ResourceWorkflowsTab` and override `get_workflow_types`: + +```python +# myapp/controllers/resources/tabs/project_workflows_tab.py +from tethysext.atcore.controllers.resources import ResourceWorkflowsTab +from myapp_adapter.workflows import LEAF_WORKFLOWS, PARENT_WORKFLOWS + + +class ProjectWorkflowsTab(ResourceWorkflowsTab): + def get_workflow_types(self, request, resource): + return PARENT_WORKFLOWS if resource.children else LEAF_WORKFLOWS +``` + +The base returns a global `{TYPE: WorkflowClass}` dict; the override gates the launcher menu by resource state. + +## 4. Compose the details controller + +```python +# myapp/controllers/resources/project_details.py +from tethysext.atcore.controllers.resources import ( + TabbedResourceDetails, ResourceFilesTab, +) +from .tabs.project_summary_tab import ProjectSummaryTab +from .tabs.project_workflows_tab import ProjectWorkflowsTab + + +class ProjectDetails(TabbedResourceDetails): + template_name = 'myapp/project_details.html' + tabs = ( + {'slug': 'summary', 'title': 'Summary', 'view': ProjectSummaryTab}, + {'slug': 'files', 'title': 'Files', 'view': ResourceFilesTab}, + {'slug': 'workflows', 'title': 'Workflows', 'view': ProjectWorkflowsTab}, + ) + + def get_context(self, request, session, resource, context, *args, **kwargs): + context = super().get_context(request, session, resource, context, *args, **kwargs) + # Gate UI on atcore-provided permissions. + from tethys_sdk.permissions import has_permission + context['can_manage_resources'] = has_permission(request, 'edit_resource') + return context +``` + +## 5. Provide the template + +The default tabbed template lives at `atcore/resources/tabbed_resource_details.html`. Extend it and override `app_navigation_items` (or any other block) to add app-specific chrome: + +```html +{# tethysapp/myapp/templates/myapp/project_details.html #} +{% extends 'atcore/resources/tabbed_resource_details.html' %} + +{% block app_navigation_items %} + <li class="nav-item"> + <a class="nav-link" href="{% url 'myapp:projects_manage_resources' %}"> + All projects + </a> + </li> +{% endblock %} +``` + +## 6. Register the URL by hand + +`TabbedResourceDetails` needs a `{tab_slug}` URL kwarg. The `app_users.urls(custom_resources=...)` and `resources.urls(...)` helpers don't emit it, so add the URL map yourself in `register_url_maps`: + +```python +# myapp/app.py +from .controllers.resources.project_details import ProjectDetails +from myapp_adapter.resources.project import Project +from myapp_adapter.app_users.permissions import MyPermissionsManager +from myapp_adapter.app_users.organization import MyOrganization + +url_maps += [ + UrlMap( + name='project_details_tab', + url='projects/{resource_id}/{tab_slug}', + controller=ProjectDetails.as_controller( + _app=self, + _persistent_store_name='primary_db', + _Organization=MyOrganization, + _Resource=Project, + _PermissionsManager=MyPermissionsManager, + ), + ), +] +``` + +The leading-underscore kwargs to `as_controller(...)` fill the view-mixin slots that atcore's controllers depend on. The URL helpers do this automatically — when you register a URL by hand, you do it yourself. + +## 7. Link to a specific tab + +```python +from django.urls import reverse +url = reverse('myapp:project_details_tab', kwargs={ + 'resource_id': resource.id, + 'tab_slug': 'workflows', +}) +``` + +The default redirect after a `ResourceDetails` action goes to `<slug>_resource_details`. Override `default_back_url` on your workflow router (and similar) so the user lands on the tab they came from. + +## See also + +- [`TabbedResourceDetails`](../api/controllers/resources/tabbed_resource_details.mdx) and the per-tab classes under [`controllers.resources.tabs`](../api/controllers/resources/tabs/index.mdx). +- [Controllers concept](../concepts/controllers.md#tabbed-resource-details). diff --git a/website/docs/how-to/build-a-resource-workflow.md b/website/docs/how-to/build-a-resource-workflow.md new file mode 100644 index 00000000..11598116 --- /dev/null +++ b/website/docs/how-to/build-a-resource-workflow.md @@ -0,0 +1,220 @@ +--- +id: how-to-build-a-resource-workflow +title: Build a Resource Workflow +sidebar_label: Build a resource workflow +sidebar_position: 2 +--- + +# Build a resource workflow + +Compose a custom workflow from the built-in step types, then register its URLs. + +## 1. Subclass `ResourceWorkflow` with a `new()` factory + +Define a `new()` classmethod that takes the runtime context (app, resource id, GeoServer name, map and spatial managers) and returns an unsaved workflow with its step graph populated. atcore's URL helpers and controllers expect this shape. + +```python +# myapp_adapter/workflows/analysis.py +from tethysext.atcore.models.app_users import ResourceWorkflow +from tethysext.atcore.models.resource_workflow_steps import ( + SpatialInputRWS, FormInputRWS, SpatialCondorJobRWS, ResultsResourceWorkflowStep, +) +from tethysext.atcore.services.app_users.roles import Roles + + +def build_jobs_callback(workflow, step, *args, **kwargs): + """Returns the CondorWorkflowJobNode dict list for this run.""" + return [] # replace with real job specs + + +class AnalysisWorkflow(ResourceWorkflow): + TYPE = 'analysis' + DISPLAY_TYPE_SINGULAR = 'Analysis' + DISPLAY_TYPE_PLURAL = 'Analyses' + __mapper_args__ = {'polymorphic_identity': TYPE} + + @classmethod + def new(cls, app, name, resource_id, creator_id, + geoserver_name, map_manager, spatial_manager, **kwargs): + workflow = cls(name=name, resource_id=resource_id, creator_id=creator_id) + + pick = SpatialInputRWS( + name='Pick study area', + order=1, + help='Draw or upload your area of interest.', + options={ + 'shapes': ['polygons', 'extents'], + 'singular_name': 'Area of Interest', + 'plural_name': 'Areas of Interest', + 'allow_shapefile': True, + 'allow_drawing': True, + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + + configure = FormInputRWS( + name='Configure inputs', + order=2, + options={ + # Dot-path string — the form module is imported lazily so it + # can in turn import other domain models without circulars. + 'param_class': 'myapp_adapter.workflows.analysis.options.AnalysisOptions', + 'form_title': 'Analysis Options', + 'renderer': 'django', + }, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + + run = SpatialCondorJobRWS( + name='Run analysis', + order=3, + options={ + 'scheduler': app.SCHEDULER_NAME, + 'jobs': build_jobs_callback, # callable, not a static list + 'workflow_kwargs': {}, + 'working_message': 'Running...', + 'error_message': 'Failed.', + 'pending_message': 'Pending.', + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + run.parents.append(pick) + + results = ResultsResourceWorkflowStep(name='Results', order=4) + run.result = results # singular `result`, not `results.append` + + workflow.steps.extend([pick, configure, run, results]) + return workflow +``` + +Two bits of wiring beyond `workflow.steps.extend(...)`: + +- `run.parents.append(pick)` declares that the condor step depends on the spatial-input step. Step views read `parents` to fetch upstream data (e.g., the polygon the user drew). +- `run.result = results` ties a job step to the page that displays its output. It's singular — don't append to `workflow.results`; the condor manager populates that list when the job finishes. + +## 2. Build the form options class + +`FormInputRWS.options['param_class']` is a dot-path string. atcore imports the class lazily after the workflow module finishes loading, so the form module can import other domain models without circulars: + +```python +# myapp_adapter/workflows/analysis/options.py +import param + + +class AnalysisOptions(param.Parameterized): + duration_days = param.Integer(default=30, bounds=(1, 365), doc='Analysis duration') + include_uncertainty = param.Boolean(default=False) +``` + +atcore's `param_widgets` translate `param.Parameterized` fields into Django form fields. + +## 3. Instantiate the workflow + +Call `new()` from a controller or a Django shell: + +```python +# example — controllers/start_analysis.py +from myapp_adapter.workflows.analysis import AnalysisWorkflow + +workflow = AnalysisWorkflow.new( + app=app, + name=f'Analysis for {project.name}', + resource_id=project.id, + creator_id=request.user.username, + geoserver_name='primary_geoserver', + map_manager=map_manager, + spatial_manager=spatial_manager, +) +session.add(workflow) +session.commit() +``` + +## 4. Register the workflow router + +```python +# myapp/app.py — register_url_maps +from tethysext.atcore.urls import resource_workflows as rw_urls +from tethysext.atcore.controllers.resource_workflows import ResourceWorkflowRouter +from myapp_adapter.workflows.analysis import AnalysisWorkflow + + +def register_url_maps(self): + UrlMap = url_map_maker(self.root_url) + + return tuple(rw_urls.urls( + url_map_maker=UrlMap, + app=self, + persistent_store_name='primary_db', + workflow_pairs=((AnalysisWorkflow, ResourceWorkflowRouter),), + base_template='myapp/workflows_base.html', + )) +``` + +The router emits three URL maps per workflow type: + +- `<workflow_type>_workflow` +- `<workflow_type>_workflow_step` +- `<workflow_type>_workflow_step_result` + +Call `rw_urls.urls(...)` once per workflow type. The per-call options (template, custom models, permissions manager) usually differ. + +## 5. Link a user into the workflow + +```python +from django.shortcuts import redirect, reverse + +return redirect(reverse( + 'myapp:analysis_workflow', + kwargs={'resource_id': resource.id, 'workflow_id': workflow.id}, +)) +``` + +The router loads the workflow, picks the current step (the first one not yet complete), and dispatches to the appropriate view from [`workflow_views`](../api/controllers/resource_workflows/workflow_views/index.mdx) or [`map_workflows`](../api/controllers/resource_workflows/map_workflows/index.mdx). + +## 6. Customizing a step view + +Subclass the view for your step base, override the hook you need, and point the step's `CONTROLLER` attribute (a dot-path string) at the subclass. + +```python +# myapp/controllers/workflow_steps/picky_spatial_input_mwv.py +from tethysext.atcore.controllers.resource_workflows.map_workflows import ( + SpatialInputMWV, +) + + +class PickyspatialInputMWV(SpatialInputMWV): + template_name = 'myapp/workflow_steps/picky_spatial_input.html' + + def process_step_data(self, request, session, step, *args, **kwargs): + # Validate, transform, or augment the submitted features here. + return super().process_step_data(request, session, step, *args, **kwargs) +``` + +Set `CONTROLLER` on the step type to the dot-path of the view: + +```python +# myapp_adapter/workflow_steps/picky_spatial_input_rws.py +from tethysext.atcore.models.resource_workflow_steps import SpatialInputRWS + + +class PickySpatialInputRWS(SpatialInputRWS): + CONTROLLER = 'myapp.controllers.workflow_steps.picky_spatial_input_mwv.PickyspatialInputMWV' + TYPE = 'picky_spatial_input' + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +The router dispatches each step via `CONTROLLER`. There's no class-level dict to populate on `ResourceWorkflowRouter`, and you don't need to subclass the router unless you want to override behavior like `default_back_url(request, resource_id)`. + +For defining a new step base or attribute schema, see [Add a custom workflow step type](./add-a-custom-workflow-step-type.md). + +## See also + +- [Resource Workflows concept page](../concepts/resource-workflows.md) — the `new()` factory contract and step-options patterns. +- [Run a Condor Workflow Job](./run-a-condor-workflow-job.md) — `SpatialCondorJobRWS` specifics. +- [Permissions](../concepts/permissions.md) — `can_override_user_locks` (the workflow-lock override). diff --git a/website/docs/how-to/customize-a-map-view.md b/website/docs/how-to/customize-a-map-view.md new file mode 100644 index 00000000..a78a57fe --- /dev/null +++ b/website/docs/how-to/customize-a-map-view.md @@ -0,0 +1,164 @@ +--- +id: how-to-customize-a-map-view +title: Customize a Map View +sidebar_label: Customize a map view +sidebar_position: 3 +--- + +# Customize a map view + +Build a `MapView` page for a custom resource. You'll need a [`Resource`](../concepts/resources.md) subclass and a [`SpatialManager`](../concepts/services.md) subclass. + +## 1. Define a SpatialManager and MapManager + +Add layers in `compose_map(...)`. It returns a `(MapView, extent, layer_groups)` tuple — the `MapView` controller unpacks all three. `layer_groups` is a list built via `build_layer_group(...)` and powers the layer-toggle UI; pass `[]` if you don't want grouping. + +```python +# myapp/services/spatial.py +from geoalchemy2.shape import to_shape +from shapely.geometry import mapping +from tethys_sdk.gizmos import MapView, MVLayer, MVView +from tethysext.atcore.services.base_spatial_manager import BaseSpatialManager +from tethysext.atcore.services.map_manager import MapManagerBase + + +class MyAppSpatialManager(BaseSpatialManager): + WORKSPACE = 'myapp' + URI = 'http://app.aquaveo.com/myapp' + + +class MyAppMapManager(MapManagerBase): + DEFAULT_CENTER = [-98.5, 39.5] + DEFAULT_ZOOM = 4 + + def compose_map(self, request, resource_id=None, *args, **kwargs): + view = MVView( + projection='EPSG:4326', + center=self.DEFAULT_CENTER, + zoom=self.DEFAULT_ZOOM, + maxZoom=self.MAX_ZOOM, + minZoom=self.MIN_ZOOM, + ) + + layers = [] + + # 1) A static GeoJSON layer for the resource's area-of-interest. + if self.resource and self.resource.area_of_interest is not None: + layers.append(self._compose_aoi_layer(self.resource)) + + # 2) A WMS layer published by GeoServer for a project basemap. + wms_url = self.spatial_manager.get_ows_endpoint() + '/wms' + layers.append(MVLayer( + source='ImageWMS', + options={ + 'url': wms_url, + 'params': {'LAYERS': f'{self.spatial_manager.WORKSPACE}:streams'}, + 'serverType': 'geoserver', + 'crossOrigin': 'anonymous', + }, + legend_title='Streams', + layer_options={'visible': True}, + )) + + # 3) Group layers for the layer-toggle UI. + layer_groups = [ + self.build_layer_group( + id='resource_layers', + display_name=f'{self.resource.name} layers', + layers=layers, + layer_control='checkbox', + ), + ] + + # The MapView controller overwrites controls / legend / height / width + # / disable_basemap on the returned MapView — don't bother setting them. + map_view = MapView(view=view, layers=layers, basemap='OpenStreetMap') + + extent = self.get_map_extent() + return map_view, extent, layer_groups + + def _compose_aoi_layer(self, resource): + shape = to_shape(resource.area_of_interest) + geojson = { + 'type': 'FeatureCollection', + 'crs': {'type': 'name', 'properties': {'name': 'EPSG:4326'}}, + 'features': [{ + 'type': 'Feature', + 'geometry': mapping(shape), + 'properties': {'name': resource.name}, + }], + } + return MVLayer( + source='GeoJSON', + options=geojson, + legend_title=f'{resource.name} — AOI', + layer_options={'style': {'fill': {'color': 'rgba(52,152,219,0.4)'}}}, + ) +``` + +The base class also exposes [`COLOR_RAMPS`](../api/services/color_ramps.mdx) for thematic styling, plus `build_layer_group(...)` and `build_legend_item(...)` helpers for the layer-toggle UI. + +:::tip Where layer URLs come from +For PostGIS-backed layers published through `ModelDBSpatialManager`, the WMS / WFS URL points at GeoServer with the resource's `ModelDatabase` as the datastore. For static GeoJSON, build the geometry in-process and hand it to `MVLayer(source='GeoJSON', options={...})`. Mixing both on one map is fine. +::: + +## 2. Subclass `MapView` + +```python +# myapp/controllers/project_map.py +from tethysext.atcore.controllers.map_view import MapView +from ..services.spatial import MyAppMapManager, MyAppSpatialManager + + +class ProjectMap(MapView): + map_title = 'Project Map' + map_subtitle = 'Inputs and outputs' + template_name = 'myapp/project_map.html' + geoserver_name = 'primary_geoserver' # SpatialDatasetServiceSetting name + + _MapManager = MyAppMapManager + _SpatialManager = MyAppSpatialManager + + geocode_enabled = True + properties_popup_enabled = True + show_legends = True +``` + +`MapView` wires the auth check, resource lookup, and slide sheet; override only what you need. + +Common hooks: + +- `get_context(request, session, resource, context, *args, **kwargs)` — extend the template context. +- `get_map_manager(request, resource, *args, **kwargs)` — return a custom manager instance per request. +- `should_disable_basemap(request, resource, map_manager)` — toggle the basemap. +- `on_get` — short-circuit a GET before the default rendering. POST handlers go through `request_to_method` (the `method` POST parameter selects the handler by name). + +## 3. Reuse `MapView`'s template + +The default template is `atcore/map_view/map_view.html`. To extend it, set `template_name` on your subclass and `{% extends 'atcore/map_view/map_view.html' %}` in your template, overriding the blocks you want. + +For tweaks like the page subtitle or layer-tab name, just set the class attributes (`map_subtitle`, `layer_tab_name`) and keep the default template. + +## 4. Register the URL + +```python +# myapp/app.py +from .controllers.project_map import ProjectMap + +UrlMap( + name='project_map', + url='projects/{resource_id}/map', + controller=ProjectMap.as_controller( + _app=self, + _persistent_store_name='primary_db', + ), +) +``` + +`MapView` extends [`ResourceView`](../api/controllers/resource_view.mdx#resourceview), so `_app` and `_persistent_store_name` are required `as_controller` kwargs. If you subclassed `Resource`, also pass `_Resource=Project` so the controller resolves your subclass on lookup. + +## See also + +- [`MapView`](../api/controllers/map_view.mdx#mapview) class docs. +- [Services](../concepts/services.md) — choosing between `ResourceSpatialManager` and `ModelDBSpatialManager`. +- [`SlideSheet`](../concepts/gizmos.md) — already integrated into `MapView` for layer / feature details. diff --git a/website/docs/how-to/extend-the-spatial-manager.md b/website/docs/how-to/extend-the-spatial-manager.md new file mode 100644 index 00000000..28d32d40 --- /dev/null +++ b/website/docs/how-to/extend-the-spatial-manager.md @@ -0,0 +1,80 @@ +--- +id: how-to-extend-the-spatial-manager +title: Extend the Spatial Manager +sidebar_label: Extend the spatial manager +sidebar_position: 6 +--- + +# Extend the spatial manager + +The spatial manager is the GeoServer-facing helper for atcore's map views. Subclass [`BaseSpatialManager`](../api/services/base_spatial_manager.mdx) (or one of its more specific subclasses) and customize the workspace, URI, SLD path, and any layer publishing operations. + +## Pick the right base class + +| Use this | When | +| --- | --- | +| [`BaseSpatialManager`](../api/services/base_spatial_manager.mdx) | You're rolling your own without atcore's model/file database integration. | +| [`ResourceSpatialManager`](../api/services/resource_spatial_manager.mdx) | Your layers are scoped to a `Resource`. | +| [`ModelDBSpatialManager`](../api/services/model_db_spatial_manager.mdx) | Your layers are scoped to a [`ModelDatabase`](../concepts/services.md). | +| [`ModelFileDBSpatialManager`](../api/services/model_file_db_spatial_manager.mdx) | Your layers are scoped to a `ModelDatabase` plus a `FileDatabase`. | + +## Minimal subclass + +```python +# my_first_app/services/spatial.py +from tethysext.atcore.services.base_spatial_manager import BaseSpatialManager + + +class MyFirstSpatialManager(BaseSpatialManager): + WORKSPACE = 'my_first_app' + URI = 'http://app.aquaveo.com/my_first_app' + GEOSERVER_CLUSTER_PORTS = (8081, 8082, 8083, 8084) + + SLD_PATH = '/path/to/your/sld/templates' + SQL_PATH = '/path/to/your/sql' +``` + +The class attributes drive every GeoServer call the base class makes. Override them at the class level, or inject them via the constructor in your subclass. + +## Use `reload_config` for mutating methods + +When you write a method that mutates GeoServer (publishing a layer, updating a style), wrap it with the [`reload_config`](../api/services/base_spatial_manager.mdx) decorator from the same module. After the method runs, the decorator calls `self.reload(ports=self.GEOSERVER_CLUSTER_PORTS, public_endpoint=...)` so all nodes pick up the change. + +```python +from tethysext.atcore.services.base_spatial_manager import BaseSpatialManager, reload_config + + +class MyFirstSpatialManager(BaseSpatialManager): + WORKSPACE = 'my_first_app' + + @reload_config() + def publish_dam_layer(self, resource, **kwargs): + # Publish layer through self.gs_engine ... + return layer +``` + +The decorator inspects `kwargs['reload_config']` if present, otherwise falls back to `reload_config_default=True`. Pass `reload_config=False` from the caller to skip the reload (useful when you'll publish many layers in a loop and reload once at the end). + +## Initialize the workspace and styles + +The `atcore` console command sets up a default workspace + styles for the global `'atcore'` workspace: + +```bash +atcore init --gsurl http://admin:geoserver@localhost:8181/geoserver/rest/ +``` + +For your app's own workspace, call `engine.create_workspace(self.WORKSPACE, self.URI)` from a one-time admin script or extend `init_atcore` ([`tethysext.atcore.cli.init_command`](../api/cli/init_command.mdx)) for a custom CLI. + +## Wire it into a `MapView` + +```python +from tethysext.atcore.controllers.map_view import MapView +from .services.spatial import MyFirstSpatialManager, MyFirstMapManager + + +class ProjectMap(MapView): + _MapManager = MyFirstMapManager + _SpatialManager = MyFirstSpatialManager +``` + +See [Customize a Map View](./customize-a-map-view.md) for the full controller wiring. diff --git a/website/docs/how-to/run-a-condor-workflow-job.md b/website/docs/how-to/run-a-condor-workflow-job.md new file mode 100644 index 00000000..e440e6a4 --- /dev/null +++ b/website/docs/how-to/run-a-condor-workflow-job.md @@ -0,0 +1,194 @@ +--- +id: how-to-run-a-condor-workflow-job +title: Run a Condor Workflow Job +sidebar_label: Run a Condor workflow job +sidebar_position: 5 +--- + +# Run a Condor workflow job + +atcore submits long-running jobs through HTCondor and updates the resource (or workflow step) status when they finish. Two paths: + +- [`ResourceCondorWorkflow`](../api/services/resource_condor_workflow.mdx) — for initialization jobs run when **creating** a `Resource`. +- [`ResourceWorkflowCondorJobManager`](../api/services/workflow_manager/condor_workflow_manager.mdx) — for jobs run inside a [Resource Workflow](../concepts/resource-workflows.md) as a `SpatialCondorJobRWS` step. + +Both create Tethys Condor workflow jobs. They differ in what they wire status updates back to. + +## Resource initialization (one-time, on create) + +Use this when a brand-new `Resource` needs setup before it's usable. + +```python +# example — controllers/modify_project.py +from tethysext.atcore.services.resource_condor_workflow import ResourceCondorWorkflow + +def submit_init(app, request, resource, scheduler, job_manager, workspace_path, session): + rcw = ResourceCondorWorkflow( + app=app, + user=request.user, + workflow_name=f'init-{resource.name}', + workspace_path=workspace_path, + resource_db_url=str(session.get_bind().url), + resource=resource, + scheduler=scheduler, + job_manager=job_manager, + status_keys=['init'], + ) + # Override get_jobs() in a subclass to return your CondorWorkflowJobNode list. + return rcw +``` + +`status_keys` lists the keys atcore checks via `resource.get_status(key)`. Your job script must set each one to a value in [`Resource.OK_STATUSES`](../api/mixins/status_mixin.mdx) for the resource to flip from `STATUS_PENDING` to `STATUS_AVAILABLE`. + +atcore ships [`tethysext.atcore.job_scripts.update_resource_status`](https://github.com/Aquaveo/tethysext-atcore/blob/master/tethysext/atcore/job_scripts/update_resource_status.py) for status updates; Condor calls it when the workflow finishes. + +## Workflow step jobs (`SpatialCondorJobRWS`) + +When a workflow reaches a [`SpatialCondorJobRWS`](../api/models/resource_workflow_steps/spatial_condor_job_rws.mdx) step, atcore instantiates a `ResourceWorkflowCondorJobManager`. You hook in via the `jobs` callable on the step's `options` dict. + +### The `jobs` callback pattern + +`SpatialCondorJobRWS.options['jobs']` is a callable, not a static list. atcore calls it at submit time with the live workflow context, and it returns a list of dicts matching `condorpy_template_name` job specs. + +```python +# myapp_adapter/workflows/analysis/jobs.py +import os + + +def build_jobs_callback(workflow, step, *args, **kwargs): + """Build the CondorWorkflowJobNode dict list for this run. + + Called at submit time so the callback can read the latest workflow + state (e.g., form-input values from upstream FormInputRWS steps). + """ + # Pull upstream form input via the parents the step declares. + form_step = next(p for p in step.parents if p.name == 'Configure inputs') + options = form_step.get_attribute('form-values') or {} + + job_executable_dir = os.path.dirname(__file__) + + return [ + { + 'name': 'run_analysis', + 'condorpy_template_name': 'vanilla_transfer_files', + 'attributes': { + 'executable': os.path.join(job_executable_dir, 'run_analysis.py'), + 'transfer_input_files': '($transfer_input_files)', + 'transfer_output_files': 'results.json,output.tif', + }, + 'remote_input_files': [], + 'remote_output_files': ['results.json', 'output.tif'], + }, + ] +``` + +Wire it onto the step in your `ResourceWorkflow.new()` factory: + +```python +SpatialCondorJobRWS( + name='Run analysis', + order=2, + options={ + 'scheduler': app.SCHEDULER_NAME, + 'jobs': build_jobs_callback, + 'workflow_kwargs': {'max_jobs': {'analysis': 4}}, + 'working_message': 'Running analysis...', + 'error_message': 'Analysis failed.', + 'pending_message': 'Analysis pending.', + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], +) +``` + +### What atcore stamps onto each job + +`ResourceWorkflowCondorJobManager` appends a positional argument list to every job in the workflow. Your job script reads them off `sys.argv`: + +``` +resource_db_url, model_db_url, +resource_id, resource_workflow_id, resource_workflow_step_id, +gs_private_url, gs_public_url, +resource_class, workflow_class, +... any extra positional args you passed ... +``` + +Use `tethysext.atcore.job_scripts.update_resource_status` from your job script to write status back to the workflow step: + +```python +#!/usr/bin/env python +# myapp_adapter/job_scripts/run_analysis.py +import sys +from tethysext.atcore.job_scripts.update_resource_status import update_resource_status + + +def main(): + args = sys.argv[1:] + resource_db_url = args[0] + resource_workflow_step_id = args[4] + + try: + # ... do the actual work, write outputs ... + status = 'STATUS_COMPLETE' + except Exception: + status = 'STATUS_ERROR' + + update_resource_status( + resource_db_url=resource_db_url, + resource_workflow_step_id=resource_workflow_step_id, + status=status, + ) + + +if __name__ == '__main__': + main() +``` + +### Opting out of the standard arg list + +If a job wraps a third-party CLI and shouldn't receive atcore's positional args, set `use_atcore_args=False` on the `CondorWorkflowJobNode` in your callback. The job is submitted without the stamped args, and you handle status updates yourself. + +## Calling the manager directly + +To submit a Condor workflow outside the `SpatialCondorJobRWS` flow (rare), call the manager yourself: + +```python +# example — inside a custom step view +from tethysext.atcore.services.workflow_manager.condor_workflow_manager import ( + ResourceWorkflowCondorJobManager, +) + +manager = ResourceWorkflowCondorJobManager( + session=session, + resource=resource, + resource_workflow_step=step, + user=request.user, + working_directory=app.get_app_workspace().path, + app=app, + scheduler_name=app.SCHEDULER_NAME, + jobs=build_jobs_callback(workflow, step), + input_files=[], + gs_engine=app.get_spatial_dataset_service('primary_geoserver', as_engine=True), + resource_workflow=workflow, +) + +manager.prepare() +manager.run_job() +``` + +## Where the workspace lives + +Both managers create a workspace directory under `working_directory`. For step jobs the layout is: + +``` +<working_directory>/<workflow_id>/<step_id>/<safe_job_name>/ +``` + +`safe_job_name` is the step name with non-alphanumeric characters replaced by underscores. + +## See also + +- [Resource Workflows](../concepts/resource-workflows.md) — the surrounding workflow infrastructure. +- [Services](../concepts/services.md) — `ModelDatabase` (used to stamp `model_db_url` on the manager). diff --git a/website/docs/how-to/wire-up-a-file-database.md b/website/docs/how-to/wire-up-a-file-database.md new file mode 100644 index 00000000..631748c2 --- /dev/null +++ b/website/docs/how-to/wire-up-a-file-database.md @@ -0,0 +1,207 @@ +--- +id: how-to-wire-up-a-file-database +title: Wire Up a File Database +sidebar_label: Wire up a file database +sidebar_position: 9 +--- + +# Wire up a file database + +Attach a [`FileDatabase`](../concepts/file-database.md) to a custom `Resource` and expose its files through `ResourceFilesTab` and the upload/download views. + +The pattern below fits apps that own large input/output trees per resource. For a simpler "one collection of files per resource," skip the per-resource `FileDatabase` and mix in `FileCollectionMixin` instead — see Pattern B in the [File Database concept page](../concepts/file-database.md#pattern-b-per-collection-attachment). + +## 1. Configure the file-database root + +Pick a location on disk for the file databases. The convention is an `FDB_ROOT_DIR` env var pointing at a shared volume: + +```bash +export FDB_ROOT_DIR=/var/lib/myapp/file_dbs +``` + +Add the directory to your deployment manifest (Helm chart, Compose file, etc.) and make sure the Tethys process can write to it. + +## 2. Add a `file_database` relationship to your `Resource` + +```python +# myapp_adapter/resources/project.py +import os +from sqlalchemy import Column, ForeignKey +from sqlalchemy.orm import relationship +from tethysext.atcore.models.app_users import Resource +from tethysext.atcore.models.file_database import FileDatabase +from tethysext.atcore.models.types.guid import GUID +from tethysext.atcore.services.file_database import FileDatabaseClient + + +class Project(Resource): + TYPE = 'project' + DISPLAY_TYPE_SINGULAR = 'Project' + DISPLAY_TYPE_PLURAL = 'Projects' + __mapper_args__ = {'polymorphic_identity': TYPE} + + file_database_id = Column(GUID, ForeignKey('file_databases.id')) + file_database = relationship(FileDatabase) + + @classmethod + def new(cls, session, name, **kwargs): + project = cls(name=name, **kwargs) + client = FileDatabaseClient.new( + session=session, + root_directory=os.environ['FDB_ROOT_DIR'], + meta={'project_name': name}, + ) + project.file_database = client.instance + session.add(project) + return project + + @property + def file_database_client(self): + return FileDatabaseClient( + session=Session.object_session(self), + root_directory=os.environ['FDB_ROOT_DIR'], + file_database_id=self.file_database_id, + ) +``` + +`Project.new()` creates the on-disk database alongside the row. Call it from your `ModifyResource` controller's `handle_resource_finished_processing` hook so new projects always have a backing `FileDatabase`. + +## 3. Add child resources that own their own collections + +When the project has datasets that each own a collection of files, define a `Dataset` that mixes in `FileCollectionMixin`: + +```python +# myapp_adapter/resources/dataset.py +from tethysext.atcore.mixins.file_collection_mixin import FileCollectionMixin +from tethysext.atcore.models.app_users import Resource + + +class Dataset(Resource, FileCollectionMixin): + TYPE = 'dataset' + DISPLAY_TYPE_SINGULAR = 'Dataset' + DISPLAY_TYPE_PLURAL = 'Datasets' + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +Import the mixin from the submodule directly — it isn't re-exported from `tethysext.atcore.mixins.__init__` to avoid a circular import. + +## 4. Create collections under the project's file database + +In a workflow step or controller, attach a `FileCollection` to a `Dataset` under the parent `Project`'s `FileDatabase`: + +```python +# myapp/controllers/datasets/upload_dataset.py +from tethysext.atcore.services.file_database import FileCollectionClient + + +def attach_dataset_files(session, project, dataset, uploaded_files): + client = FileCollectionClient.new( + session=session, + file_database_client=project.file_database_client, + meta={'kind': 'inputs', 'dataset_id': str(dataset.id)}, + ) + for upload in uploaded_files: + # add_item takes a path on disk and copies the file into + # the collection's UUID-named directory. + client.add_item(upload.temporary_file_path()) + + dataset.file_collections.append(client.instance) + session.commit() +``` + +The on-disk layout becomes: + +``` +$FDB_ROOT_DIR/ + <project.file_database_id>/ + <collection_uuid_1>/ ← Dataset A + input.tif + boundary.shp + <collection_uuid_2>/ ← Dataset B + meteo.nc +``` + +## 5. Wire the `Manage*` controller for cleanup + +Mix `FileCollectionsControllerMixin` into the dataset's `ManageResources` controller so deleting a `Dataset` also drops the collection directory: + +```python +# myapp/controllers/datasets/manage_datasets.py +from tethysext.atcore.controllers.app_users import ManageResources +from tethysext.atcore.mixins.file_collection_controller_mixin import ( + FileCollectionsControllerMixin, +) + + +class ManageDatasets(ManageResources, FileCollectionsControllerMixin): + pass +``` + +Register it via the per-resource controller list when wiring URLs: + +```python +# myapp/app.py +from myapp_adapter.resources.project import Project +from myapp_adapter.resources.dataset import Dataset +from .controllers.projects.manage_projects import ManageProjects, ModifyProject +from .controllers.datasets.manage_datasets import ManageDatasets +from .controllers.datasets.modify_dataset import ModifyDataset + +url_maps += list(app_users_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='primary_db', + custom_resources={ + Project: [ManageProjects, ModifyProject], + Dataset: [ManageDatasets, ModifyDataset], + }, + base_template='myapp/base.html', +)) +``` + +## 6. Show the files on the dataset's tabbed details page + +Add `ResourceFilesTab` to the dataset's `TabbedResourceDetails` (see [Add a tabbed resource details page](./add-a-tabbed-resource-details-page.md) for the full pattern): + +```python +class DatasetDetails(TabbedResourceDetails): + template_name = 'myapp/dataset_details.html' + tabs = ( + {'slug': 'summary', 'title': 'Summary', 'view': DatasetSummaryTab}, + {'slug': 'files', 'title': 'Files', 'view': ResourceFilesTab}, + ) +``` + +`ResourceFilesTab` reads `dataset.file_collections` (from `FileCollectionMixin`) and renders an upload/download UI per collection. + +## 7. Async deletion for big projects + +Deleting a `Project` with hundreds of datasets and gigabytes of files can take a while. Override `ManageResources._handle_delete` to flip the status to `STATUS_DELETING` and spawn a daemon `Thread` for the cleanup: + +```python +# myapp/controllers/projects/manage_resource_delete_mixin.py +import threading +from tethysext.atcore.mixins.status_mixin import StatusMixin + + +class ManageResourceDeleteMixin: + def _handle_delete(self, request, session, resource, *args, **kwargs): + resource.set_status(StatusMixin.STATUS_DELETING) + session.commit() + threading.Thread( + target=self.delete_resource_artifacts, + args=(resource.id,), + daemon=True, + ).start() + return self._delete_response(request, resource) + + def delete_resource_artifacts(self, resource_id): + # Drop on-disk collections, GeoServer layers, Condor workspaces, ... + ... +``` + +Mix it into your `ManageProjects` controller. The user gets an immediate redirect and the cleanup runs in the background. + +## See also + +- [File Database concept page](../concepts/file-database.md) — the underlying models and exception types. +- [Add a custom resource type](./add-a-resource-type.md) — for the basic Resource subclassing recipe. diff --git a/website/docs/intro.md b/website/docs/intro.md new file mode 100644 index 00000000..4ef9e466 --- /dev/null +++ b/website/docs/intro.md @@ -0,0 +1,41 @@ +--- +id: intro +title: Introduction +sidebar_label: Introduction +sidebar_position: 0 +slug: / +--- + +# tethysext-atcore + +`tethysext-atcore` is a [Tethys Platform](https://docs.tethysplatform.org) extension that ships reusable building blocks for data-driven scientific web apps: an app-user / organization / resource model, a generic resource-workflow engine, map and resource views, gizmos, spatial managers, and a file database. Use it as the foundation for an Aquaveo-style Tethys app instead of re-deriving these pieces in every project. + +## What's inside + +- **A project layout convention** — atcore apps typically split into a Tethys package and a sibling adapter package. See [Project Structure](./concepts/project-structure.md). +- **App users, organizations, and resources** — SQLAlchemy models plus controllers and URL helpers for managing them. See [App Users](./concepts/app-users.md) and [Resources](./concepts/resources.md). +- **Resource workflows** — a step-based engine for guiding users through multi-step processes (form input, spatial input, condor jobs, results). See [Resource Workflows](./concepts/resource-workflows.md). +- **Map and resource views** — base [controllers](./concepts/controllers.md) (`MapView`, `ResourceView`) you subclass to render resource-aware pages. +- **Spatial and model database services** — managers for GeoServer layers and per-resource model databases. See [Services](./concepts/services.md). +- **Permissions** — a license/role permissions matrix and an `AppPermissionsManager`. See [Permissions](./concepts/permissions.md). +- **Gizmos** — `SlideSheet`, `SpatialReferenceSelect`. See [Gizmos](./concepts/gizmos.md). +- **File database** — a SQL-tracked filesystem store. See [File Database](./concepts/file-database.md). + +## How to read these docs + +This site follows a [Diátaxis](https://diataxis.fr)-flavored layout: + +- **[Getting Started](./getting-started/installation.md)** — install the extension and stand up a minimal Tethys app that uses it. +- **[Concepts](./concepts/overview.md)** — what each subsystem is for and how they fit together. +- **[How-to Guides](./how-to/add-a-resource-type.md)** — task-oriented recipes (subclass a resource, build a workflow, run a condor job). +- **[Tutorials](./tutorials/walkthrough.md)** — an end-to-end walkthrough of building a small atcore-based app. +- **[Reference](./reference/permissions-cheatsheet.md)** — quick-lookup tables for permissions and exceptions. +- **[API Reference](./api/index.mdx)** — auto-generated module documentation. Edit Python docstrings, not those pages. + +:::tip +If you've never built a Tethys app, read the [Tethys Platform tutorial](https://docs.tethysplatform.org/en/stable/tutorials.html) first. These docs assume you already understand `TethysAppBase`, `UrlMap`, persistent stores, and gizmos. +::: + +## Source + +The Python source lives at [`tethysext/atcore/`](https://github.com/Aquaveo/tethysext-atcore/tree/master/tethysext/atcore). When narrative docs and source disagree, the source wins — please open an issue or PR. diff --git a/website/docs/reference/_category_.json b/website/docs/reference/_category_.json new file mode 100644 index 00000000..1b337d5a --- /dev/null +++ b/website/docs/reference/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Reference", + "position": 5 +} diff --git a/website/docs/reference/exceptions.md b/website/docs/reference/exceptions.md new file mode 100644 index 00000000..3dace819 --- /dev/null +++ b/website/docs/reference/exceptions.md @@ -0,0 +1,72 @@ +--- +id: reference-exceptions +title: Exceptions +sidebar_label: Exceptions +sidebar_position: 2 +--- + +# Exceptions + +Quick reference for the exceptions defined in [`tethysext.atcore.exceptions`](../api/exceptions/index.mdx). All eleven live in a single module; class hierarchy and "raised when" notes below. + +## Class hierarchy + +```text +Exception +├── ATCoreException +│ └── ModelDatabaseError +│ ├── ModelDatabaseInitializationError +│ └── ModelFileDatabaseInitializationError +├── UnboundFileCollectionError +├── UnboundFileDatabaseError +├── FileCollectionNotFoundError +├── FileCollectionItemNotFoundError +├── FileDatabaseNotFoundError +├── FileCollectionItemAlreadyExistsError +└── InvalidSpatialResourceExtentTypeError +``` + +## When each is raised + +| Exception | Raised when | +| --- | --- | +| `ATCoreException` | Base class for a subset of ATCore exceptions. Catch this for user-actionable errors that explicitly inherit from `ATCoreException` (for example model-database errors). The [`resource_controller`](../api/services/app_users/decorators.mdx) decorator handles those by surfacing `str(e)` as a Django message. | +| `ModelDatabaseError` | Generic problem talking to a `ModelDatabase`. | +| `ModelDatabaseInitializationError` | A `ModelDatabase` could not be created or initialized. | +| `ModelFileDatabaseInitializationError` | A `ModelFileDatabase` could not be created or initialized. | +| `UnboundFileDatabaseError` | A `FileDatabaseClient` operation ran after the underlying database was deleted. See [`FileDatabaseClient.instance`](../api/services/file_database.mdx#filedatabaseclient). | +| `UnboundFileCollectionError` | A `FileCollectionClient` operation ran after the underlying collection was deleted. | +| `FileDatabaseNotFoundError` | The id passed to `FileDatabaseClient` did not match any row. | +| `FileCollectionNotFoundError` | The id passed to `FileCollectionClient` did not match any row. | +| `FileCollectionItemNotFoundError` | The requested item is not in the collection. | +| `FileCollectionItemAlreadyExistsError` | An item with that name is already in the collection. | +| `InvalidSpatialResourceExtentTypeError` | `SpatialResource.set_extent` got an `object_format` other than `'wkt'`, `'geojson'`, or `'dict'`. See [`SpatialResource`](../api/models/app_users/spatial_resource.mdx). | + +## Catching them + +The most common pattern: let the [`resource_controller`](../api/services/app_users/decorators.mdx) decorator catch `ATCoreException` so it shows up as a user-visible warning. Raise `ATCoreException("Reason")` from your view code when the failure is expected and user-actionable. For file-database and collection exceptions, catch the specific exception classes directly. + +```python +from tethysext.atcore.exceptions import ATCoreException + +if some_invariant_violated: + raise ATCoreException('Project must have a region before submitting an analysis.') +``` + +For file database operations, catch the specific `Not Found` / `AlreadyExists` errors: + +```python +from tethysext.atcore.exceptions import ( + FileCollectionItemNotFoundError, FileCollectionItemAlreadyExistsError, +) + +try: + collection_client.add_item(path) +except FileCollectionItemAlreadyExistsError: + ... +``` + +## See also + +- [File Database](../concepts/file-database.md) — uses most of the file-related exceptions. +- [Resources](../concepts/resources.md) — `SpatialResource` raises `InvalidSpatialResourceExtentTypeError`. diff --git a/website/docs/reference/permissions-cheatsheet.md b/website/docs/reference/permissions-cheatsheet.md new file mode 100644 index 00000000..afe30fea --- /dev/null +++ b/website/docs/reference/permissions-cheatsheet.md @@ -0,0 +1,122 @@ +--- +id: reference-permissions-cheatsheet +title: Permissions Cheat Sheet +sidebar_label: Permissions cheat sheet +sidebar_position: 1 +--- + +# Permissions cheat sheet + +Quick reference for the role / license matrix and the permissions in each group. The source of truth is [`tethysext/atcore/permissions/app_users.py`](https://github.com/Aquaveo/tethysext-atcore/blob/master/tethysext/atcore/permissions/app_users.py); this page summarizes it. + +## Roles + +From [`Roles`](../api/services/app_users/roles.mdx): + +| Constant | String | Rank | +| --- | --- | --- | +| `ROLES.ORG_USER` | `user_role_org_user` | 100 | +| `ROLES.ORG_REVIEWER` | `user_role_org_reviewer` | 200 | +| `ROLES.ORG_ADMIN` | `user_role_org_admin` | 300 | +| `ROLES.APP_ADMIN` | `user_role_app_admin` | 1000 | +| `ROLES.DEVELOPER` | `user_role_developer` | inf | + +## Licenses + +From [`Licenses`](../api/services/app_users/licenses.mdx): + +| Constant | String | Rank | Can have clients? | Can have consultant? | +| --- | --- | --- | --- | --- | +| `LICENSES.STANDARD` | `standard` | 100 | no | yes | +| `LICENSES.ADVANCED` | `advanced` | 200 | no | yes | +| `LICENSES.PROFESSIONAL` | `professional` | 300 | no | yes | +| `LICENSES.CONSULTANT` | `consultant` | 400 | yes | no | + +## Permission groups + +[`AppPermissionsManager`](../api/services/app_users/permissions_manager.mdx#apppermissionsmanager) names them with the convention `<license>_<role>_perms`, namespaced to your app: + +| Constant | Group name | Notes | +| --- | --- | --- | +| `STD_U_PERMS` | `standard_user_perms` | Standard org user. | +| `STD_R_PERMS` | `standard_reviewer_perms` | Standard org reviewer. | +| `STD_A_PERMS` | `standard_admin_perms` | Standard org admin. | +| `ADV_U_PERMS` | `advanced_user_perms` | | +| `ADV_R_PERMS` | `advanced_reviewer_perms` | | +| `ADV_A_PERMS` | `advanced_admin_perms` | | +| `PRO_U_PERMS` | `professional_user_perms` | | +| `PRO_R_PERMS` | `professional_reviewer_perms` | | +| `PRO_A_PERMS` | `professional_admin_perms` | | +| `CON_U_PERMS` | `consultant_user_perms` | | +| `CON_R_PERMS` | `consultant_reviewer_perms` | | +| `CON_A_PERMS` | `consultant_admin_perms` | Adds `create_organizations`, `edit_organizations`, license-assign perms. | +| `APP_A_PERMS` | `app_admin_perms` | Has _every_ permission. | + +Higher-tier groups are supersets of lower-tier groups in the same role column. + +## Permissions index + +| Permission | Description | +| --- | --- | +| `view_all_resources` | View all resources. | +| `view_resources` | View resources. | +| `view_resource_details` | View details for resources. | +| `create_resource` | Create resources. | +| `edit_resource` | Edit resources. | +| `delete_resource` | Delete resources. | +| `always_delete_resource` | Delete resource even if not editable. | +| `view_users` | View app users. | +| `view_all_users` | View all users. | +| `modify_users` | Edit, delete, create app users. | +| `modify_user_manager` | Modify the manager of a user. | +| `assign_org_user_role` | Assign organization user role. | +| `assign_org_reviewer_role` | Assign organization reviewer role. | +| `assign_org_admin_role` | Assign organization admin role. | +| `assign_app_admin_role` | Assign app admin role. | +| `assign_developer_role` | Assign developer role. | +| `view_organizations` | View organizations. | +| `view_all_organizations` | View any organization. | +| `create_organizations` | Edit, delete, create organizations. | +| `edit_organizations` | Edit organizations. | +| `delete_organizations` | Delete organizations. | +| `modify_organization_members` | Assign / remove members. | +| `assign_any_resource` | Assign any resource to organizations. | +| `assign_any_user` | Assign any user to organizations. | +| `assign_any_organization` | Assign any organization to resources. | +| `assign_standard_license` | Assign standard license. | +| `assign_advanced_license` | Assign advanced license. | +| `assign_professional_license` | Assign professional license. | +| `assign_consultant_license` | Assign consultant license. | +| `assign_any_license` | Assign any license. | +| `remove_layers` | Remove layers from map views. | +| `rename_layers` | Rename layers from map views. | +| `toggle_public_layers` | Toggle layers for public viewing. | +| `use_map_plot` | Use the plotting feature on map views. | +| `use_map_geocode` | Use the geocoding feature on map views. | +| `can_override_user_locks` | Override user locks on workflows. | +| `can_download` | Download layer in map view. | +| `can_export_datatable` | Export data in datatable. | + +## Common lookups + +```python +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager +from tethysext.atcore.services.app_users.roles import Roles +from tethysext.atcore.services.app_users.licenses import Licenses + +pm = AppPermissionsManager('my_first_app') + +pm.STANDARD_ADMIN_PERMS +# 'my_first_app:standard_admin_perms' + +pm.get_permissions_group_for(role=Roles.ORG_ADMIN, license=Licenses.PROFESSIONAL) +# 'my_first_app:professional_admin_perms' + +pm.list(with_namespace=True) +# All enabled, namespaced groups. +``` + +## See also + +- [Permissions](../concepts/permissions.md) — concept-level explanation. +- [`PermissionsGenerator`](../api/permissions/app_users.mdx#permissionsgenerator) — registration entry point. diff --git a/website/docs/tutorials/_category_.json b/website/docs/tutorials/_category_.json new file mode 100644 index 00000000..b9dada94 --- /dev/null +++ b/website/docs/tutorials/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Tutorials", + "position": 4 +} diff --git a/website/docs/tutorials/walkthrough.md b/website/docs/tutorials/walkthrough.md new file mode 100644 index 00000000..299d71ae --- /dev/null +++ b/website/docs/tutorials/walkthrough.md @@ -0,0 +1,476 @@ +--- +id: tutorials-walkthrough +title: End-to-End Walkthrough +sidebar_label: Walkthrough +sidebar_position: 1 +--- + +# End-to-end walkthrough: a project + analysis app + +A small atcore-backed Tethys app, from scratch. A `Project` resource, the app-user pages, a map page that actually shows a layer, and a one-step analysis workflow that submits a Condor job. Touches every major atcore subsystem. + +If you're new to Tethys, finish [the Tethys tutorial](https://docs.tethysplatform.org/en/stable/tutorials.html) first. This walkthrough assumes you can already register URL maps, configure persistent stores, and build a Tethys gizmo template. + +:::tip Single-package vs. two-package layout +Larger atcore apps split the domain layer into a sibling adapter package — see [Project Structure](../concepts/project-structure.md). To keep things short, this walkthrough uses a single package. Refactor into two once your model and workflow code is more than a screenful. +::: + +## Prerequisites + +- atcore [installed](../getting-started/installation.md) into your Tethys environment. +- A PostgreSQL + PostGIS database reachable via a Tethys persistent store. +- A GeoServer instance reachable from Tethys. +- An HTCondor scheduler reachable from Tethys (only needed for the workflow step at the end). + +## 1. Scaffold the app + +```bash +tethys scaffold my_first_app +cd tethysapp-my_first_app +tethys install -d +``` + +Add atcore to your `install.yml` requirements if it isn't already pulled in transitively. + +## 2. Define `Project` + +Three pieces: a polymorphic identity, a geometry column for the area-of-interest, and a status key for "is this ready to use." Most other state stays on the inherited JSON `_attributes` blob. + +```python +# my_first_app/models/__init__.py +from .projects import Project # noqa +from .workflows import AnalysisWorkflow # noqa +``` + +```python +# my_first_app/models/projects.py +from geoalchemy2 import Geometry +from sqlalchemy import Column +from tethysext.atcore.models.app_users import Resource + + +class Project(Resource): + TYPE = 'project' + DISPLAY_TYPE_SINGULAR = 'Project' + DISPLAY_TYPE_PLURAL = 'Projects' + + STATUS_KEY_INIT = 'init' + + area_of_interest = Column(Geometry('POLYGON', 4326)) + + __mapper_args__ = {'polymorphic_identity': TYPE} +``` + +## 3. Define `AnalysisWorkflow` + +Workflows expose a `new()` classmethod that builds the step graph from runtime context. The atcore `WorkflowsTab` launcher calls `WorkflowClass.new(app=..., name=..., resource_id=..., creator_id=..., geoserver_name=..., map_manager=..., spatial_manager=...)`, so subclasses that want to be created from that tab must accept those kwargs. See [the `new()` factory](../concepts/resource-workflows.md#the-new-factory). + +`ResourceWorkflow.get_url_name()` is abstract — your subclass must return the namespaced URL name your app registers for this workflow type. + +```python +# my_first_app/models/workflows.py +from tethysext.atcore.models.app_users import ResourceWorkflow +from tethysext.atcore.models.resource_workflow_steps import ( + SpatialInputRWS, + SpatialCondorJobRWS, + ResultsResourceWorkflowStep, +) +from tethysext.atcore.services.app_users.roles import Roles + + +def build_jobs_callback(manager): + """Return at least one CondorWorkflowJobNode dict. + + SpatialCondorJobRWS calls this at submit time so the callback can read + live workflow state. Returning an empty list raises in + ResourceWorkflowCondorJobManager.validate_jobs — the manager always + requires at least one job. + """ + return [ + { + # Replace the executable with a real script in your app workspace. + # The 'noop' template runs /bin/true and returns immediately, which + # is enough to advance the workflow on a working Condor scheduler. + 'name': 'noop', + 'condorpy_template_name': 'vanilla', + 'attributes': { + 'executable': '/bin/true', + }, + 'remote_input_files': [], + }, + ] + + +class AnalysisWorkflow(ResourceWorkflow): + TYPE = 'analysis' + DISPLAY_TYPE_SINGULAR = 'Analysis' + DISPLAY_TYPE_PLURAL = 'Analyses' + __mapper_args__ = {'polymorphic_identity': TYPE} + + def get_url_name(self): + return f'my_first_app:{self.TYPE}_workflow' + + @classmethod + def new(cls, app, name, resource_id, creator_id, + geoserver_name, map_manager, spatial_manager, **kwargs): + workflow = cls(name=name, resource_id=resource_id, creator_id=creator_id) + + pick_step = SpatialInputRWS( + name='Pick study area', + order=1, + help='Draw or upload your area of interest.', + options={ + 'shapes': ['polygons', 'extents'], + 'singular_name': 'Area of Interest', + 'plural_name': 'Areas of Interest', + 'allow_shapefile': True, + 'allow_drawing': True, + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + + run_step = SpatialCondorJobRWS( + name='Run analysis', + order=2, + options={ + 'scheduler': app.SCHEDULER_NAME, + 'jobs': build_jobs_callback, + 'workflow_kwargs': {}, + 'working_message': 'Running analysis...', + 'error_message': 'Analysis failed.', + 'pending_message': 'Analysis pending.', + }, + geoserver_name=geoserver_name, + map_manager=map_manager, + spatial_manager=spatial_manager, + active_roles=[Roles.ORG_USER, Roles.ORG_ADMIN], + ) + run_step.parents.append(pick_step) + + results_step = ResultsResourceWorkflowStep( + name='Results', + order=3, + ) + run_step.result = results_step + + workflow.steps.extend([pick_step, run_step, results_step]) + return workflow +``` + +:::caution Condor is required for this step +`SpatialCondorJobRWS` always submits to a Condor scheduler — atcore validates the jobs list before submitting and `[]` raises `ValueError`. If you don't have HTCondor wired up, swap the `SpatialCondorJobRWS` step for a [`SetStatusRWS`](../api/models/resource_workflow_steps/set_status_rws.mdx) that flips straight to `STATUS_COMPLETE` so you can still walk the workflow end-to-end. +::: + +## 4. Build a SpatialManager and MapManager + +The MapManager renders a static GeoJSON layer for the project's `area_of_interest` so step 7 has something to verify against — a polygon should appear when you open the map page. + +```python +# my_first_app/services/spatial.py +from geoalchemy2.shape import to_shape +from shapely.geometry import mapping +from tethys_sdk.gizmos import MapView, MVLayer, MVView +from tethysext.atcore.services.base_spatial_manager import BaseSpatialManager +from tethysext.atcore.services.map_manager import MapManagerBase + + +class MyFirstSpatialManager(BaseSpatialManager): + WORKSPACE = 'my_first_app' + URI = 'http://app.aquaveo.com/my_first_app' + + +class MyFirstMapManager(MapManagerBase): + DEFAULT_CENTER = [-98.5, 39.5] # rough US centroid + DEFAULT_ZOOM = 4 + MAX_ZOOM = 18 + MIN_ZOOM = 2 + + def compose_map(self, request, *args, **kwargs): + view = MVView( + projection='EPSG:4326', + center=self.DEFAULT_CENTER, + zoom=self.DEFAULT_ZOOM, + maxZoom=self.MAX_ZOOM, + minZoom=self.MIN_ZOOM, + ) + + layers = [] + if self.resource and self.resource.area_of_interest is not None: + shape = to_shape(self.resource.area_of_interest) + geojson = { + 'type': 'FeatureCollection', + 'crs': {'type': 'name', 'properties': {'name': 'EPSG:4326'}}, + 'features': [{ + 'type': 'Feature', + 'geometry': mapping(shape), + 'properties': {'name': self.resource.name}, + }], + } + aoi_layer = MVLayer( + source='GeoJSON', + options=geojson, + legend_title=f'{self.resource.name} — area of interest', + layer_options={'style': {'fill': {'color': 'rgba(52,152,219,0.4)'}}}, + feature_selection=False, + ) + layers.append(aoi_layer) + + layer_groups = [ + self.build_layer_group( + id='project_layers', + display_name='Project', + layers=layers, + layer_control='checkbox', + ), + ] + + map_view = MapView(view=view, layers=layers, basemap='OpenStreetMap') + + # MapManagerBase.compose_map returns (MapView, extent, layer_groups). + # The MapView controller overwrites controls/legend/height/width on + # the returned MapView, so don't bother setting those here. + extent = [-180.0, -90.0, 180.0, 90.0] + return map_view, extent, layer_groups +``` + +A fresh project with no `area_of_interest` renders an empty map over the basemap. That's the expected first-load state. + +## 5. Build a `MapView` controller + +```python +# my_first_app/controllers/project_map.py +from tethysext.atcore.controllers.map_view import MapView +from ..services.spatial import MyFirstMapManager, MyFirstSpatialManager + + +class ProjectMap(MapView): + map_title = 'Project Map' + map_subtitle = '' + template_name = 'atcore/map_view/map_view.html' + geoserver_name = 'primary_geoserver' + _MapManager = MyFirstMapManager + _SpatialManager = MyFirstSpatialManager +``` + +`geoserver_name` must match the `SpatialDatasetServiceSetting` you'll register in step 7. The `MapView` controller resolves it via `app.get_spatial_dataset_service(self.geoserver_name, as_engine=True)`, so leaving it at the default empty string raises before `compose_map` runs. + +## 6. Build a "start workflow" controller + +Normally this would be a button on the project details page or the workflows tab. Here it's just a URL you hit manually. + +```python +# my_first_app/controllers/start_analysis.py +from django.http import HttpResponseRedirect +from django.urls import reverse +from tethys_sdk.routing import controller +from tethysext.atcore.models.app_users import AppUser + +from ..app import MyFirstApp as app +from ..models import Project, AnalysisWorkflow +from ..services.spatial import MyFirstMapManager, MyFirstSpatialManager + + +@controller( + name='start_analysis', + url='my-first-app/projects/{resource_id}/analysis/start', + app_workspace=False, +) +def start_analysis(request, resource_id): + Session = app.get_persistent_store_database('app_users_db', as_sessionmaker=True) + session = Session() + try: + project = session.query(Project).get(resource_id) + # ResourceWorkflow.creator_id is a UUID FK to AppUser, not a username. + app_user = AppUser.get_app_user_from_request(request, session) + spatial_manager = MyFirstSpatialManager(geoserver_engine=None) + map_manager = MyFirstMapManager(spatial_manager=spatial_manager, resource=project) + + workflow = AnalysisWorkflow.new( + app=app, + name=f'Analysis for {project.name}', + resource_id=project.id, + creator_id=app_user.id, + geoserver_name='primary_geoserver', + map_manager=map_manager, + spatial_manager=spatial_manager, + ) + session.add(workflow) + session.commit() + + return HttpResponseRedirect(reverse( + 'my_first_app:analysis_workflow', + kwargs={'resource_id': project.id, 'workflow_id': workflow.id}, + )) + finally: + session.close() +``` + +The redirect target name comes from the URL helper. The pattern is `<workflow_type>_workflow` and `AnalysisWorkflow.TYPE` is `'analysis'`. + +## 7. Wire `app.py` + +Four atcore URL helpers, one app. `app_users.urls(custom_resources={...})` registers the user, organization, and `Project` CRUD pages in a single call. + +```python +# my_first_app/app.py +from tethys_sdk.app_settings import ( + PersistentStoreDatabaseSetting, + SchedulerSetting, + SpatialDatasetServiceSetting, +) +from tethys_sdk.base import TethysAppBase, url_map_maker +from tethysext.atcore.controllers.resource_workflows import ResourceWorkflowRouter +from tethysext.atcore.models.app_users import initialize_app_users_db +from tethysext.atcore.permissions.app_users import PermissionsGenerator +from tethysext.atcore.services.app_users.permissions_manager import AppPermissionsManager +from tethysext.atcore.urls import ( + app_users as app_users_urls, + resource_workflows as rw_urls, + spatial_reference as sr_urls, +) + +from .controllers.project_map import ProjectMap +from .models import AnalysisWorkflow, Project + + +def init_app_users_db(engine, first_time): + # Importing the models inside the initializer ensures the SQLAlchemy + # mappers are registered before create_all runs. + from .models import projects, workflows # noqa + initialize_app_users_db(engine, first_time=first_time) + + +class MyFirstApp(TethysAppBase): + name = 'My First App' + package = 'my_first_app' + namespace = 'my_first_app' + root_url = 'my-first-app' + + SCHEDULER_NAME = 'remote_cluster' + + def persistent_store_settings(self): + return ( + PersistentStoreDatabaseSetting( + name='app_users_db', + description='atcore app-users database', + initializer='my_first_app.app.init_app_users_db', + spatial=True, + required=True, + ), + ) + + def spatial_dataset_service_settings(self): + return ( + SpatialDatasetServiceSetting( + name='primary_geoserver', + description='Primary GeoServer', + engine=SpatialDatasetServiceSetting.GEOSERVER, + required=False, + ), + ) + + def scheduler_settings(self): + return ( + SchedulerSetting( + name=self.SCHEDULER_NAME, + description='HTCondor scheduler', + engine=SchedulerSetting.CONDOR, + required=False, + ), + ) + + def permissions(self): + pm = AppPermissionsManager(self.namespace) + return PermissionsGenerator(pm).generate() + + def register_url_maps(self): + # Call super() so Tethys discovers @controller-decorated functions + # (the start_analysis view in step 6 won't register otherwise). + url_maps = list(super().register_url_maps(set_index=False)) + + UrlMap = url_map_maker(self.root_url) + url_maps += [ + UrlMap( + name='project_map', + url='projects/{resource_id}/map', + controller=ProjectMap.as_controller( + _app=self, + _persistent_store_name='app_users_db', + ), + ), + ] + + # User / organization / Project CRUD in one call. + url_maps += list(app_users_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + custom_resources={Project: []}, + base_template='my_first_app/base.html', + )) + + url_maps += list(rw_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + workflow_pairs=((AnalysisWorkflow, ResourceWorkflowRouter),), + base_template='my_first_app/base.html', + )) + + url_maps += list(sr_urls.urls( + url_map_maker=UrlMap, app=self, + persistent_store_name='app_users_db', + )) + + return tuple(url_maps) +``` + +## 8. Sync and explore + +```bash +tethys syncstores my_first_app +tethys manage start +``` + +Then: + +1. Visit `/apps/my-first-app/users/` → atcore's `ManageUsers` page. +2. Visit `/apps/my-first-app/organizations/` → `ManageOrganizations`. Create an organization. +3. Visit `/apps/my-first-app/projects/` → `ManageResources` for `Project`. Click **New** and create a project. The form takes a name and description; the area-of-interest gets set later via a custom modify form. +4. Click into the project, then visit `/apps/my-first-app/projects/<resource_id>/map` to see the map page. +5. Hit `/apps/my-first-app/projects/<resource_id>/analysis/start` to launch an `AnalysisWorkflow`. atcore redirects into the `ResourceWorkflowRouter`, which dispatches to the `SpatialInputRWS` view. + +### Expected map state + +- Fresh project (no AOI): OpenStreetMap basemap centered on the US, no overlay layers, empty legend. +- Project with AOI: basemap plus a translucent blue polygon for the area-of-interest, with the project's name in the legend. + +If the map page returns a 500, the usual culprit is a missing `MyFirstApp.permissions()` registration. atcore's `MapView` uses `active_user_required`, which expects the permission groups to exist. + +## 9. Where the workflow goes from here + +Once you redirect into the router: + +- It picks the first incomplete step (the `SpatialInputRWS` named "Pick study area"). +- It dispatches to `controllers.resource_workflows.map_workflows.SpatialInputMWV`, which renders a draw-tools map. +- After you draw a polygon and submit, the `SpatialCondorJobRWS` named "Run analysis" becomes active. +- Submitting that step calls `build_jobs_callback` and submits the returned jobs to your Condor scheduler. The `noop` job in the example finishes immediately; atcore writes `STATUS_COMPLETE` back to the step and reveals the `ResultsResourceWorkflowStep`. + +Swap `build_jobs_callback` for real `CondorWorkflowJobNode` dicts pointing at your own scripts to make the step do actual work — see [Run a Condor Workflow Job](../how-to/run-a-condor-workflow-job.md). + +## What you built + +- A custom `Resource` (`Project`) with a PostGIS geometry column, registered through `app_users.urls(custom_resources=...)`. +- A custom `MapView` backed by your own `MapManager` and `SpatialManager`, rendering a GeoJSON layer. +- A custom `ResourceWorkflow` (`AnalysisWorkflow`) using the `new()` factory contract — three steps, role-gated, with `parents` and `result` wired up. +- A "start workflow" controller that materializes a workflow and redirects into the router. +- App-users / organizations / spatial-reference pages from atcore's `urls(...)` factories. + +## Where to go next + +- [Project Structure](../concepts/project-structure.md) — refactoring into a two-package adapter layout once the app grows. +- [Add a tabbed resource details page](../how-to/add-a-tabbed-resource-details-page.md) — replace the default project details with `TabbedResourceDetails`. +- [Add a custom workflow step type](../how-to/add-a-custom-workflow-step-type.md) — when the built-in step types don't fit. +- [Wire up a file database](../how-to/wire-up-a-file-database.md) — give `Project` on-disk inputs. +- [Build a Resource Workflow](../how-to/build-a-resource-workflow.md) and [Run a Condor Workflow Job](../how-to/run-a-condor-workflow-job.md). diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 00000000..60839b46 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,100 @@ +// @ts-check +// Docusaurus 3.x configuration for the tethysext-atcore documentation site. + +const { themes: prismThemes } = require('prism-react-renderer'); + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: 'tethysext-atcore', + tagline: 'A Tethys Platform extension providing reusable controllers, services, and gizmos.', + + url: 'https://aquaveo.github.io', + baseUrl: '/tethysext-atcore/', + + organizationName: 'Aquaveo', + projectName: 'tethysext-atcore', + + trailingSlash: false, + onBrokenLinks: 'throw', + markdown: { + hooks: { + onBrokenMarkdownLinks: 'warn', + }, + }, + + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + routeBasePath: '/', + sidebarPath: require.resolve('./sidebars.js'), + editUrl: 'https://github.com/Aquaveo/tethysext-atcore/edit/master/website/', + }, + blog: false, + theme: { + customCss: require.resolve('./src/css/custom.css'), + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + navbar: { + title: 'tethysext-atcore', + items: [ + { + type: 'docSidebar', + sidebarId: 'tutorialSidebar', + position: 'left', + label: 'Docs', + }, + { + type: 'docSidebar', + sidebarId: 'apiSidebar', + position: 'left', + label: 'API', + }, + { + href: 'https://github.com/Aquaveo/tethysext-atcore', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'light', + links: [ + { + title: 'Project', + items: [ + { + label: 'GitHub', + href: 'https://github.com/Aquaveo/tethysext-atcore', + }, + { + label: 'License', + href: 'https://github.com/Aquaveo/tethysext-atcore/blob/master/LICENSE', + }, + ], + }, + ], + copyright: `Copyright \u00a9 ${new Date().getFullYear()} Aquaveo, LLC. Built with Docusaurus.`, + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + additionalLanguages: ['bash', 'python', 'json', 'yaml'], + }, + }), +}; + +module.exports = config; diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 00000000..31f589f2 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,18393 @@ +{ + "name": "tethysext-atcore-website", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tethysext-atcore-website", + "version": "0.0.0", + "dependencies": { + "@docusaurus/core": "^3.10.0", + "@docusaurus/preset-classic": "^3.10.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "prism-react-renderer": "^2.3.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.17.0.tgz", + "integrity": "sha512-nuhHZdTiCtRzJEe9VSNzyqE9cOQMt01UWBzymFnjbgwrxxZpbGHQde6Oa/y9zyspTCjbUtb7Q5HQek1CLiLyeg==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.8.tgz", + "integrity": "sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.8", + "@algolia/autocomplete-shared": "1.19.8" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.8.tgz", + "integrity": "sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.8" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz", + "integrity": "sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==", + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.51.0.tgz", + "integrity": "sha512-PKrKlIla1U2J7mFcIQn6N3pWP4oySmkxShnbbDsj/H7818gKbET5KsUwsVoNjWIxHKTJMCTcQ7ekAJ8Ea23NMg==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.51.0.tgz", + "integrity": "sha512-U+HCY1K16Km91pIRL1kN6bW6BbGFAF/WhkRSCx4wyl1aFpbrlhSFQs/dAwWbmyBiHWwVWhl7stWHQ1pum5EfMw==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.51.0.tgz", + "integrity": "sha512-YPJ3dEuZLCRp846Az94t6Z2gwSNRazP+SmBco7p6SCa4fYrtIE820PDXYZshbNrj2Z8Qfbmv7BQ1Lecl5L3G/w==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.51.0.tgz", + "integrity": "sha512-/gEwLlR7fQ7YjOW+ADRZ0NxLDtpTC61FSzlZ01Gdl1kTJfU0Rq3Y/TYqwxGxlQGcUiXtGzrpjxXWh3Y0TZD6NA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.51.0.tgz", + "integrity": "sha512-nRwUN1Y2cKyOAFZyIBagkEfZSIhP05nWhT4Rjwl84lcjECssYggftrAODrZ4leakXxSGjhxs/AdaAFEIBqwVFA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.51.0.tgz", + "integrity": "sha512-pybzYCG7VoQKppo+z5iZOKpW8XqtFxhsAIRgEaNboCnfypKukiBHJAwB+pmr7vMZXBsOHwklGYWwCG82e8qshA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.51.0.tgz", + "integrity": "sha512-DWVIlj6RqcvdhwP0gBU9OpOQPnHdcAk9jlT+z8rsNb2+liWv4eUlfQZ7saGBraFsnygEHD3PtdppIHvqwBAb5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" + }, + "node_modules/@algolia/ingestion": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.51.0.tgz", + "integrity": "sha512-bA25s12iUDJi/X8M7tWlPRT8GeOhls/yDbdoUqinz27lNqsOlcM1UrAxIKdIZ6lm3sXit+ewPIz1pS2x6rXu8g==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.51.0.tgz", + "integrity": "sha512-zj+RcE5e0NE0/ew6oEOTgOplPHry+w2oi7h0Y87lhdq4E0d7xLS31KVB8kKfCGkrG7AYtZvrcyvLOKS5d0no4Q==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.51.0.tgz", + "integrity": "sha512-/HDgccfye1Rq3bPxaSCsvSEBHzSMmtpM9ZRGRtAuC62Cv+ql/76IWnxjGTDXtqIJ+/j7ZlFYAzq9fkp95wF2SQ==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.51.0.tgz", + "integrity": "sha512-nJdW+WBwGlXWMJbxxB7/AJPvNq0lLJSudXmIQCJbmH8jsOXQhRpAtoCD4ceLyJKv3ze9JbQu4Gqu5JDLckuFcw==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.51.0.tgz", + "integrity": "sha512-bsBgRI/1h1mjS3eCyfGau78yGZVmiDLmT1aU6dMnk75/T0SgKqnSKNpQ53xKoDYVChGDcNnpHXWpoUSo8MH1+w==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.51.0.tgz", + "integrity": "sha512-zPrIDVPpmKWgrjmWOqpqrhqAhNjvVkjoj+mqw2NBPxEOuKNzP0H+Qz5NJLLTOepBVj1UFedFaF3AUgxLsB9ukQ==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", + "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", + "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", + "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", + "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", + "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", + "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-contrast-color-function": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", + "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", + "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", + "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", + "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", + "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", + "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", + "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", + "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", + "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", + "integrity": "sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", + "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-position-area-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-position-area-property/-/postcss-position-area-property-1.0.0.tgz", + "integrity": "sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", + "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-property-rule-prelude-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", + "integrity": "sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", + "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", + "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", + "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", + "integrity": "sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-system-ui-font-family": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", + "integrity": "sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", + "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/core": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.6.3.tgz", + "integrity": "sha512-rUOujwIpxJRgD7+kicVsI3D5sqBvdiRTquzWBpTEXZs8ZXfGbfzpus5HqumaNYTppN2HvH8E2yNuRwYdHJeOlA==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@docsearch/css": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.3.tgz", + "integrity": "sha512-nlOwcXcsNAptQl4vlL4MA78qNJKO0Qlds5GuBjCoePgkebTXLSf8Qt1oyZ3YBshYupKXG9VRGEsk1zr23d+bzQ==", + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.6.3.tgz", + "integrity": "sha512-Bg2wdDsoQVlNCcEKuEJAU04tvHCqgx8rIu+uIoM4pRtcx3TBKJuXutJik3LTA8LRc9YEyHkrYUrmcC0D7BYf+g==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/core": "4.6.3", + "@docsearch/css": "4.6.3" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.2" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.0.tgz", + "integrity": "sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.10.0", + "@docusaurus/utils": "3.10.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.0.tgz", + "integrity": "sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.10.0", + "@docusaurus/cssnano-preset": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.0.tgz", + "integrity": "sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.10.0", + "@docusaurus/bundler": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "execa": "^5.1.1", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "open": "^8.4.0", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.3", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.7", + "tinypool": "^1.0.2", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^5.2.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*", + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz", + "integrity": "sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==", + "license": "MIT", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.5.4", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.0.tgz", + "integrity": "sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz", + "integrity": "sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz", + "integrity": "sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.10.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz", + "integrity": "sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "cheerio": "1.0.0-rc.12", + "combine-promises": "^1.1.0", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz", + "integrity": "sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz", + "integrity": "sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz", + "integrity": "sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz", + "integrity": "sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^2.3.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz", + "integrity": "sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz", + "integrity": "sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "@types/gtag.js": "^0.0.20", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz", + "integrity": "sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz", + "integrity": "sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz", + "integrity": "sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz", + "integrity": "sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/plugin-content-blog": "3.10.0", + "@docusaurus/plugin-content-docs": "3.10.0", + "@docusaurus/plugin-content-pages": "3.10.0", + "@docusaurus/plugin-css-cascade-layers": "3.10.0", + "@docusaurus/plugin-debug": "3.10.0", + "@docusaurus/plugin-google-analytics": "3.10.0", + "@docusaurus/plugin-google-gtag": "3.10.0", + "@docusaurus/plugin-google-tag-manager": "3.10.0", + "@docusaurus/plugin-sitemap": "3.10.0", + "@docusaurus/plugin-svgr": "3.10.0", + "@docusaurus/theme-classic": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/theme-search-algolia": "3.10.0", + "@docusaurus/types": "3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz", + "integrity": "sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/plugin-content-blog": "3.10.0", + "@docusaurus/plugin-content-docs": "3.10.0", + "@docusaurus/plugin-content-pages": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/theme-translations": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "copy-text-to-clipboard": "^3.2.0", + "infima": "0.2.0-alpha.45", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.5.4", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.0.tgz", + "integrity": "sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-search-algolia": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz", + "integrity": "sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "^1.19.2", + "@docsearch/react": "^3.9.0 || ^4.3.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/plugin-content-docs": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/theme-translations": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "algoliasearch": "^5.37.0", + "algoliasearch-helper": "^3.26.0", + "clsx": "^2.0.0", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-translations": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz", + "integrity": "sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==", + "license": "MIT", + "dependencies": { + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.0.tgz", + "integrity": "sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==", + "license": "MIT", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/mdast": "^4.0.2", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.95.0", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.0.tgz", + "integrity": "sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "escape-string-regexp": "^4.0.0", + "execa": "^5.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "p-queue": "^6.6.2", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-common": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.0.tgz", + "integrity": "sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==", + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.10.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-validation": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz", + "integrity": "sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", + "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", + "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", + "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", + "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", + "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", + "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.2" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", + "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.57.2", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", + "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@slorber/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/gtag.js": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.20.tgz", + "integrity": "sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "license": "MIT" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-config": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", + "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "^5.1.0" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.51.0.tgz", + "integrity": "sha512-u3XS8HaTzt5YN90KPsOXMRjYJUMVD1dtr6yi4NXQluMbZ5IjQNBu1MEabdAxFhYtEuexqomPMSmRIhQJUd3QSg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/abtesting": "1.17.0", + "@algolia/client-abtesting": "5.51.0", + "@algolia/client-analytics": "5.51.0", + "@algolia/client-common": "5.51.0", + "@algolia/client-insights": "5.51.0", + "@algolia/client-personalization": "5.51.0", + "@algolia/client-query-suggestions": "5.51.0", + "@algolia/client-search": "5.51.0", + "@algolia/ingestion": "1.51.0", + "@algolia/monitoring": "1.51.0", + "@algolia/recommend": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.28.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.28.2.tgz", + "integrity": "sha512-sexVcXLHrJN54+S0wXD52xV3ySeGZA5T6HMDkb84wT+3UcXCd8af/k2vU5qJTbHv7DoBb4mISJHdyQ2JOo3Aig==", + "license": "MIT", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/copy-text-to-clipboard": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", + "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.8.0.tgz", + "integrity": "sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", + "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "license": "MIT", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/emoticon": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", + "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "dependencies": { + "@types/node": "*", + "require-like": ">= 0.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "license": "MIT", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", + "integrity": "sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==", + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infima": { + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "license": "MIT", + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launch-editor": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", + "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-to-fsa": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "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.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.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.0.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.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-space/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "license": "MIT", + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", + "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", + "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", + "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-lab-function": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", + "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", + "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-merge-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.6.1.tgz", + "integrity": "sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-alpha-function": "^1.0.1", + "@csstools/postcss-cascade-layers": "^5.0.2", + "@csstools/postcss-color-function": "^4.0.12", + "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", + "@csstools/postcss-color-mix-function": "^3.0.12", + "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", + "@csstools/postcss-content-alt-text": "^2.0.8", + "@csstools/postcss-contrast-color-function": "^2.0.12", + "@csstools/postcss-exponential-functions": "^2.0.9", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.11", + "@csstools/postcss-gradients-interpolation-method": "^5.0.12", + "@csstools/postcss-hwb-function": "^4.0.12", + "@csstools/postcss-ic-unit": "^4.0.4", + "@csstools/postcss-initial": "^2.0.1", + "@csstools/postcss-is-pseudo-class": "^5.0.3", + "@csstools/postcss-light-dark-function": "^2.0.11", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.4", + "@csstools/postcss-media-minmax": "^2.0.9", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.1", + "@csstools/postcss-oklab-function": "^4.0.12", + "@csstools/postcss-position-area-property": "^1.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/postcss-property-rule-prelude-list": "^1.0.0", + "@csstools/postcss-random-function": "^2.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.12", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.4", + "@csstools/postcss-stepped-value-functions": "^4.0.9", + "@csstools/postcss-syntax-descriptor-syntax-production": "^1.0.1", + "@csstools/postcss-system-ui-font-family": "^1.0.0", + "@csstools/postcss-text-decoration-shorthand": "^4.0.3", + "@csstools/postcss-trigonometric-functions": "^4.0.9", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.23", + "browserslist": "^4.28.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.3", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.6.0", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.12", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.6", + "postcss-custom-properties": "^14.0.6", + "postcss-custom-selectors": "^8.0.5", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.4", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.12", + "postcss-logical": "^8.1.0", + "postcss-nesting": "^13.0.2", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-reduce-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sort-media-queries": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "license": "MIT", + "dependencies": { + "sort-css-media-queries": "2.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.23" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postcss-zindex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "name": "@slorber/react-helmet-async", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-json-view-lite": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-loadable": { + "name": "@docusaurus/react-loadable", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/react": "*" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz", + "integrity": "sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.3" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "react-loadable": "*", + "webpack": ">=4.41.1 || 5.x" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "peerDependencies": { + "react": ">=15", + "react-router": ">=5" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-emoji": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", + "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.2", + "emoticon": "^4.0.1", + "mdast-util-find-and-replace": "^3.0.1", + "node-emoji": "^2.1.0", + "unified": "^11.0.4" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "engines": { + "node": "*" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "license": "MIT" + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rtlcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-dts": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", + "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", + "license": "Apache-2.0" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", + "peer": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-handler": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.5", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.3.tgz", + "integrity": "sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-css-media-queries": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "license": "MIT", + "engines": { + "node": ">= 6.3.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/srcset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", + "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, + "node_modules/svgo": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", + "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/url-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/url-loader/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpack": { + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-middleware/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.0.tgz", + "integrity": "sha512-gHwIe1cgBvvfLeu1Yz/dcFpmHfKDVxxyqI+kzqmuxZED81z2ChxpyqPaWcNqigPywhaEke7AjSGga+kxY55gjQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpackbar": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-7.0.0.tgz", + "integrity": "sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==", + "license": "MIT", + "dependencies": { + "ansis": "^3.2.0", + "consola": "^3.2.3", + "pretty-time": "^1.1.0", + "std-env": "^3.7.0" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@rspack/core": "*", + "webpack": "3 || 4 || 5" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 00000000..7ac473db --- /dev/null +++ b/website/package.json @@ -0,0 +1,48 @@ +{ + "name": "tethysext-atcore-website", + "version": "0.0.0", + "private": true, + "description": "Documentation site for the tethysext-atcore Tethys Platform extension.", + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids", + "typecheck": "echo \"No TypeScript in this project; skipping typecheck.\"" + }, + "dependencies": { + "@docusaurus/core": "^3.10.0", + "@docusaurus/preset-classic": "^3.10.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "prism-react-renderer": "^2.3.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^3.10.0" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 3 chrome version", + "last 3 firefox version", + "last 5 safari version" + ] + }, + "engines": { + "node": ">=20.0" + }, + "overrides": { + "webpackbar": "7.0.0" + } +} diff --git a/website/scripts/generate_api_docs.py b/website/scripts/generate_api_docs.py new file mode 100644 index 00000000..022cda0b --- /dev/null +++ b/website/scripts/generate_api_docs.py @@ -0,0 +1,697 @@ +#!/usr/bin/env python3 +"""Generate Docusaurus MDX API reference for tethysext.atcore. + +This script walks the ``tethysext/atcore/`` package using only Python's +standard ``ast`` module — it never imports the project. That keeps the +docs build hermetic: it doesn't need GDAL, Tethys, Django, or any of +the other heavy runtime dependencies installed. + +Output goes under ``website/docs/api/`` mirroring the package layout. +The script is idempotent: rerunning it produces byte-identical output. + +Ordering: +- Modules and subpackages: alphabetical. +- Classes and top-level functions: source order (preserves intended + reading order from the author). +- Methods within a class: source order. +""" + +from __future__ import annotations + +import ast +import json +import re +import shutil +import sys +from dataclasses import dataclass, field +from pathlib import Path +from typing import Iterable + +# Repo paths ----------------------------------------------------------------- +REPO_ROOT = Path(__file__).resolve().parents[2] +PACKAGE_ROOT = REPO_ROOT / "tethysext" / "atcore" +API_DOCS_ROOT = REPO_ROOT / "website" / "docs" / "api" +PACKAGE_DOTTED = "tethysext.atcore" + +# Subpackages and top-level modules to include. Names match entries +# directly under ``tethysext/atcore/``. Anything not listed here is +# skipped — this keeps non-Python asset directories (templates, sql, +# job scripts, public, tests) out of the API reference. +TOP_LEVEL_TARGETS: set[str] = { + "cli", + "controllers", + "exceptions", + "forms", + "gizmos", + "handlers.py", + "mixins", + "models", + "permissions", + "services", + "urls", + "utilities.py", +} + +# Subpackage display labels (alphabetic position is 1..N below). +SUBPACKAGE_LABELS = { + "cli": "cli", + "controllers": "controllers", + "exceptions": "exceptions", + "forms": "forms", + "gizmos": "gizmos", + "handlers": "handlers", + "mixins": "mixins", + "models": "models", + "permissions": "permissions", + "resources": "resources", + "services": "services", + "urls": "urls", + "utilities": "utilities", +} + + +# --------------------------------------------------------------------------- +# AST extraction +# --------------------------------------------------------------------------- + + +@dataclass +class FunctionInfo: + name: str + signature: str + docstring: str | None + decorators: list[str] + is_async: bool + + +@dataclass +class ClassInfo: + name: str + bases: list[str] + docstring: str | None + decorators: list[str] + methods: list[FunctionInfo] = field(default_factory=list) + + +@dataclass +class ModuleInfo: + dotted_name: str # e.g. tethysext.atcore.services.foo + rel_path: Path # path under PACKAGE_ROOT, e.g. services/foo.py + docstring: str | None + classes: list[ClassInfo] = field(default_factory=list) + functions: list[FunctionInfo] = field(default_factory=list) + + +def _unparse(node: ast.AST | None) -> str: + if node is None: + return "" + try: + return ast.unparse(node) + except Exception: + return "<unparseable>" + + +def _format_arguments(args: ast.arguments) -> str: + parts: list[str] = [] + + # positional-only + posonly = list(args.posonlyargs) + regular = list(args.args) + defaults = list(args.defaults) + + total_positional = posonly + regular + # defaults align to the tail of total_positional + n_defaults = len(defaults) + n_positional = len(total_positional) + default_offset = n_positional - n_defaults + + for idx, arg in enumerate(total_positional): + piece = arg.arg + if arg.annotation is not None: + piece += f": {_unparse(arg.annotation)}" + if idx >= default_offset: + default_node = defaults[idx - default_offset] + piece += f"={_unparse(default_node)}" + parts.append(piece) + if posonly and idx == len(posonly) - 1: + parts.append("/") + + if args.vararg is not None: + v = "*" + args.vararg.arg + if args.vararg.annotation is not None: + v += f": {_unparse(args.vararg.annotation)}" + parts.append(v) + elif args.kwonlyargs: + parts.append("*") + + for kw, default in zip(args.kwonlyargs, args.kw_defaults): + piece = kw.arg + if kw.annotation is not None: + piece += f": {_unparse(kw.annotation)}" + if default is not None: + piece += f"={_unparse(default)}" + parts.append(piece) + + if args.kwarg is not None: + k = "**" + args.kwarg.arg + if args.kwarg.annotation is not None: + k += f": {_unparse(args.kwarg.annotation)}" + parts.append(k) + + return ", ".join(parts) + + +def _function_signature(node: ast.FunctionDef | ast.AsyncFunctionDef) -> str: + args = _format_arguments(node.args) + sig = f"{node.name}({args})" + if node.returns is not None: + sig += f" -> {_unparse(node.returns)}" + return sig + + +def _decorator_list(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> list[str]: + return [_unparse(d) for d in node.decorator_list] + + +def _is_public(name: str) -> bool: + # Dunder methods (e.g. __init__) are documented; single-underscore are private. + if name.startswith("__") and name.endswith("__"): + return True + return not name.startswith("_") + + +def _extract_function(node: ast.FunctionDef | ast.AsyncFunctionDef) -> FunctionInfo: + return FunctionInfo( + name=node.name, + signature=_function_signature(node), + docstring=ast.get_docstring(node, clean=True), + decorators=_decorator_list(node), + is_async=isinstance(node, ast.AsyncFunctionDef), + ) + + +def _extract_class(node: ast.ClassDef) -> ClassInfo: + bases = [_unparse(b) for b in node.bases] + methods: list[FunctionInfo] = [] + for child in node.body: + if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)): + if not _is_public(child.name): + continue + methods.append(_extract_function(child)) + return ClassInfo( + name=node.name, + bases=bases, + docstring=ast.get_docstring(node, clean=True), + decorators=_decorator_list(node), + methods=methods, + ) + + +def parse_module(path: Path, dotted_name: str) -> ModuleInfo | None: + try: + source = path.read_text(encoding="utf-8") + except (UnicodeDecodeError, OSError) as exc: + print(f"Skipping {path}: {exc}", file=sys.stderr) + return None + + try: + tree = ast.parse(source, filename=str(path)) + except SyntaxError as exc: + print(f"Syntax error in {path}: {exc}", file=sys.stderr) + return None + + module = ModuleInfo( + dotted_name=dotted_name, + rel_path=path.relative_to(PACKAGE_ROOT), + docstring=ast.get_docstring(tree, clean=True), + ) + + for node in tree.body: + if isinstance(node, ast.ClassDef): + if not _is_public(node.name): + continue + module.classes.append(_extract_class(node)) + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + if not _is_public(node.name): + continue + module.functions.append(_extract_function(node)) + + return module + + +# --------------------------------------------------------------------------- +# Filesystem walking +# --------------------------------------------------------------------------- + + +def is_skipped_dir(name: str) -> bool: + return name in {"__pycache__", "tests"} or name.endswith(".egg-info") + + +def is_module_file(path: Path) -> bool: + if path.suffix != ".py": + return False + stem = path.stem + if stem == "__init__": + return True + if stem.startswith("_"): + return False + return True + + +def iter_modules(start: Path, dotted_prefix: str) -> Iterable[tuple[Path, str]]: + """Yield (path, dotted_name) for every .py module to document.""" + for entry in sorted(start.iterdir(), key=lambda p: p.name): + if entry.is_dir(): + if is_skipped_dir(entry.name) or entry.name.startswith("."): + continue + if entry.name.startswith("_") and entry.name != "__init__.py": + continue + sub_init = entry / "__init__.py" + if not sub_init.exists(): + # not a package + continue + yield from iter_modules(entry, f"{dotted_prefix}.{entry.name}") + elif entry.is_file(): + if not is_module_file(entry): + continue + if entry.name == "__init__.py": + yield entry, dotted_prefix + else: + yield entry, f"{dotted_prefix}.{entry.stem}" + + +# --------------------------------------------------------------------------- +# MDX rendering +# --------------------------------------------------------------------------- + + +SLUG_RE = re.compile(r"[^a-z0-9]+") + + +def slugify(*parts: str) -> str: + joined = "-".join(parts) + joined = joined.lower() + joined = SLUG_RE.sub("-", joined).strip("-") + return joined or "section" + + +def escape_mdx_text(text: str) -> str: + """Make a docstring safe for MDX without altering meaning. + + Strategy: render the docstring as a fenced code block. This is + robust against ``<``, ``>``, ``{``, ``}``, backslashes, and nested + Markdown that would otherwise confuse MDX. We only need to ensure + the fence itself isn't shadowed by a longer fence inside the text. + """ + # Escape literal triple-backticks by switching the fence to a longer one. + fence = "```" + while fence in text: + fence += "`" + return f"{fence}text\n{text}\n{fence}\n" + + +def escape_inline_code(text: str) -> str: + """Wrap a piece of code (signature, base list, decorator) in inline backticks. + + If the text itself contains backticks, escape with extra backticks. + """ + if "`" not in text: + return f"`{text}`" + # Use double backticks if single backticks appear inside. + return f"`` {text} ``" + + +def heading_id(*parts: str) -> str: + return slugify(*parts) + + +def _render_decorators(decorators: list[str]) -> str: + if not decorators: + return "" + pieces = [] + for d in decorators: + pieces.append(f"`@{d}`") + return "*" + " ".join(pieces) + "*\n\n" + + +def _render_function(func: FunctionInfo, anchor: str, level: int = 4) -> list[str]: + out: list[str] = [] + hashes = "#" * level + out.append(_render_decorators(func.decorators).rstrip("\n")) + if out and out[-1]: + out.append("") + prefix = "async " if func.is_async else "" + sig_text = f"{prefix}{func.signature}" + out.append(f"{hashes} {escape_inline_code(sig_text)} \\{{#{anchor}\\}}") + out.append("") + if func.docstring: + out.append(escape_mdx_text(func.docstring)) + else: + out.append("> _No description._") + out.append("") + return out + + +def _render_class(cls: ClassInfo, module_dotted: str) -> list[str]: + out: list[str] = [] + class_anchor = heading_id(cls.name) + bases_text = f"({', '.join(cls.bases)})" if cls.bases else "" + header = f"### {escape_inline_code(cls.name + bases_text)} \\{{#{class_anchor}\\}}" + out.append(_render_decorators(cls.decorators).rstrip("\n")) + if out and out[-1]: + out.append("") + out.append(header) + out.append("") + if cls.docstring: + out.append(escape_mdx_text(cls.docstring)) + else: + out.append("> _No description._") + out.append("") + + if cls.methods: + out.append("#### Methods") + out.append("") + for method in cls.methods: + method_anchor = heading_id(cls.name, method.name) + out.extend(_render_function(method, method_anchor, level=5)) + out.append("") + return out + + +def render_module(module: ModuleInfo) -> str: + # Determine doc id: drop the package prefix, use dot-joined. + short = module.dotted_name[len(PACKAGE_DOTTED) + 1:] if module.dotted_name != PACKAGE_DOTTED else "index" + sidebar_label = short.split(".")[-1] if short != "index" else "Overview" + + lines: list[str] = [] + lines.append("---") + lines.append(f"id: {short}") + lines.append(f"title: {module.dotted_name}") + lines.append(f"sidebar_label: {sidebar_label}") + lines.append("---") + lines.append("") + lines.append(f"# `{module.dotted_name}`") + lines.append("") + if module.docstring: + lines.append(escape_mdx_text(module.docstring)) + else: + lines.append("> _No description._") + lines.append("") + + if module.classes: + lines.append("## Classes") + lines.append("") + for cls in module.classes: + lines.extend(_render_class(cls, module.dotted_name)) + lines.append("") + + if module.functions: + lines.append("## Functions") + lines.append("") + for func in module.functions: + anchor = heading_id(func.name) + lines.extend(_render_function(func, anchor, level=3)) + lines.append("") + + if not module.classes and not module.functions: + lines.append("_This module exposes no public classes or functions._") + lines.append("") + + # Normalize trailing whitespace. + text = "\n".join(line.rstrip() for line in lines).rstrip() + "\n" + return text + + +# --------------------------------------------------------------------------- +# Output organization +# --------------------------------------------------------------------------- + + +def module_output_path(module: ModuleInfo) -> Path: + """Map a module's dotted name to its MDX output path. + + Docusaurus treats ``foo/foo.mdx`` as the index of ``foo/`` — that + collides with our explicit ``foo/index.mdx``. When a leaf module + shares its parent package's name (e.g. ``models.file_database`` + inside the ``file_database`` package), we suffix the file name to + avoid the collision. + """ + rel = module.dotted_name[len(PACKAGE_DOTTED) + 1:] + parts = rel.split(".") if rel else [] + + if module.rel_path.name == "__init__.py": + # Subpackage __init__: write to <subpackage>/index.mdx + if not parts: + return API_DOCS_ROOT / "index.mdx" + return API_DOCS_ROOT.joinpath(*parts) / "index.mdx" + else: + # parts looks like ['services', 'app_users', 'django_user_services'] + if len(parts) == 1: + # Top-level module like utilities.py or handlers.py + return API_DOCS_ROOT / f"{parts[0]}.mdx" + leaf = parts[-1] + parent = parts[-2] + if leaf == parent: + leaf = f"{leaf}-module" + return API_DOCS_ROOT.joinpath(*parts[:-1]) / f"{leaf}.mdx" + + +def write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + # Force LF line endings, no trailing whitespace per line. + normalized = "\n".join(line.rstrip() for line in text.splitlines()) + if not normalized.endswith("\n"): + normalized += "\n" + path.write_text(normalized, encoding="utf-8", newline="\n") + + +def write_category(dir_path: Path, label: str, position: int) -> None: + """Write a Docusaurus _category_.json for a directory. + + If the directory has its own ``index.mdx`` we DO NOT add a + ``generated-index`` link — that would collide with the file's URL. + Sidebar collapsing and labelling still work without it. + """ + payload: dict = { + "label": label, + "position": position, + } + text = json.dumps(payload, indent=2, sort_keys=False) + "\n" + write_text(dir_path / "_category_.json", text) + + +def render_subpackage_index( + dotted_name: str, + module: ModuleInfo, + children: list[tuple[str, str]], # (label, link target relative to dir) +) -> str: + """Render a package's ``index.mdx`` from its ``__init__.py`` module. + + Includes the module-level docstring, any classes/functions defined + in ``__init__.py`` itself, and a "Modules" section listing each + sibling submodule and subpackage. + """ + short = dotted_name[len(PACKAGE_DOTTED) + 1:] + lines: list[str] = [] + lines.append("---") + lines.append(f"id: {short}.index" if short else "id: index") + lines.append(f"title: {dotted_name}") + lines.append("sidebar_label: Overview") + lines.append("sidebar_position: 0") + lines.append("---") + lines.append("") + lines.append(f"# `{dotted_name}`") + lines.append("") + if module.docstring: + lines.append(escape_mdx_text(module.docstring)) + else: + lines.append("> _No description._") + lines.append("") + + if module.classes: + lines.append("## Classes") + lines.append("") + for cls in module.classes: + lines.extend(_render_class(cls, dotted_name)) + lines.append("") + + if module.functions: + lines.append("## Functions") + lines.append("") + for func in module.functions: + anchor = heading_id(func.name) + lines.extend(_render_function(func, anchor, level=3)) + lines.append("") + + if children: + lines.append("## Modules") + lines.append("") + for label, target in sorted(children): + # Use the .mdx extension so Docusaurus resolves the link + # relative to the source file's path, not the rendered URL + # (which omits trailing slashes and breaks ./<sibling>). + lines.append(f"- [`{label}`]({target})") + lines.append("") + + text = "\n".join(line.rstrip() for line in lines).rstrip() + "\n" + return text + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + + +def clean_output_dir() -> None: + if API_DOCS_ROOT.exists(): + for child in API_DOCS_ROOT.iterdir(): + if child.is_file(): + child.unlink() + elif child.is_dir(): + shutil.rmtree(child) + else: + API_DOCS_ROOT.mkdir(parents=True, exist_ok=True) + + +def collect_modules() -> list[ModuleInfo]: + modules: list[ModuleInfo] = [] + # Walk only the configured top-level targets. + for target in sorted(TOP_LEVEL_TARGETS): + target_path = PACKAGE_ROOT / target + if target.endswith(".py"): + if not target_path.is_file(): + print(f"Missing top-level module: {target_path}", file=sys.stderr) + continue + dotted = f"{PACKAGE_DOTTED}.{target_path.stem}" + info = parse_module(target_path, dotted) + if info is not None: + modules.append(info) + else: + if not target_path.is_dir() or not (target_path / "__init__.py").exists(): + print( + f"Skipping {target}: not a Python package (no __init__.py).", + file=sys.stderr, + ) + continue + for path, dotted_name in iter_modules( + target_path, f"{PACKAGE_DOTTED}.{target}" + ): + info = parse_module(path, dotted_name) + if info is None: + continue + modules.append(info) + modules.sort(key=lambda m: m.dotted_name) + return modules + + +def main() -> int: + clean_output_dir() + modules = collect_modules() + + # Track per-directory children for subpackage index pages. + # Map dir-path -> list of (label, slug) + dir_children: dict[Path, list[tuple[str, str]]] = {} + # Map dotted package name -> (dir_path, ModuleInfo for __init__.py) + package_init: dict[str, tuple[Path, ModuleInfo]] = {} + + written_files = 0 + module_count = 0 + + for module in modules: + out_path = module_output_path(module) + is_init = module.rel_path.name == "__init__.py" + text = render_module(module) + write_text(out_path, text) + written_files += 1 + module_count += 1 + + if is_init: + package_init[module.dotted_name] = (out_path.parent, module) + else: + parent_dir = out_path.parent + short_label = out_path.stem + dir_children.setdefault(parent_dir, []).append((short_label, short_label)) + + # Each sub-package directory gets a _category_.json (alphabetic ordering). + sorted_dirs = sorted({p for p in dir_children.keys()} | {p for p, _ in package_init.values()}) + for position, dir_path in enumerate(sorted_dirs, start=1): + if dir_path == API_DOCS_ROOT: + continue + label = dir_path.name + write_category(dir_path, label, position) + + # Root: build a top-level index.mdx that links to each top-level subpackage / module. + root_children: list[tuple[str, str]] = [] + # Top-level: every entry directly under API_DOCS_ROOT + for entry in sorted(API_DOCS_ROOT.iterdir()): + if entry.is_dir(): + index_file = entry / "index.mdx" + if index_file.exists(): + root_children.append((entry.name, f"./{entry.name}/index.mdx")) + elif entry.is_file() and entry.suffix == ".mdx" and entry.stem != "index": + root_children.append((entry.stem, f"./{entry.name}")) + + root_text_lines: list[str] = [] + root_text_lines.append("---") + root_text_lines.append("id: index") + root_text_lines.append("title: API Reference") + root_text_lines.append("sidebar_label: Overview") + root_text_lines.append("sidebar_position: 1") + root_text_lines.append("slug: /api") + root_text_lines.append("---") + root_text_lines.append("") + root_text_lines.append("# API Reference") + root_text_lines.append("") + root_text_lines.append( + "Reference documentation for the public modules in " + "`tethysext.atcore`. Pages here are generated directly from " + "the project source by `website/scripts/generate_api_docs.py`. " + "Edit Python docstrings, not these files." + ) + root_text_lines.append("") + root_text_lines.append("## Subpackages and modules") + root_text_lines.append("") + for label, target in sorted(root_children): + root_text_lines.append(f"- [`{label}`]({target})") + root_text_lines.append("") + root_text = "\n".join(root_text_lines).rstrip() + "\n" + write_text(API_DOCS_ROOT / "index.mdx", root_text) + # Remove the leftover placeholder index.md if present. + placeholder = API_DOCS_ROOT / "index.md" + if placeholder.exists(): + placeholder.unlink() + + # Now overwrite each subpackage __init__ render with a richer index + # that includes both the module's own classes/functions AND a list + # of sibling submodules. + for dotted_name, (dir_path, init_module) in package_init.items(): + children: list[tuple[str, str]] = [] + for entry in sorted(dir_path.iterdir()): + if entry.is_dir(): + index_file = entry / "index.mdx" + if index_file.exists(): + children.append((entry.name, f"./{entry.name}/index.mdx")) + elif entry.is_file() and entry.suffix == ".mdx" and entry.stem != "index": + children.append((entry.stem, f"./{entry.name}")) + index_text = render_subpackage_index( + dotted_name=dotted_name, + module=init_module, + children=children, + ) + write_text(dir_path / "index.mdx", index_text) + + # Re-write root category.json (no explicit link — index.mdx handles it). + root_cat = { + "label": "API Reference", + "position": 99, + } + write_text(API_DOCS_ROOT / "_category_.json", json.dumps(root_cat, indent=2) + "\n") + + print(f"Generated docs for {module_count} modules into {API_DOCS_ROOT}") + print(f"Total files written: {written_files} (plus index/category files)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/website/sidebars.js b/website/sidebars.js new file mode 100644 index 00000000..2f9caf0d --- /dev/null +++ b/website/sidebars.js @@ -0,0 +1,52 @@ +// @ts-check +// Docusaurus sidebar configuration. +// +// `tutorialSidebar` lists the narrative sections explicitly so the API tree +// (which lives under `docs/api/`) only appears in the dedicated `apiSidebar`. +// Within each narrative section, content is autogenerated, so writers can +// drop new pages without editing this file. + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + tutorialSidebar: [ + 'intro', + { + type: 'category', + label: 'Getting Started', + collapsed: false, + items: [{ type: 'autogenerated', dirName: 'getting-started' }], + }, + { + type: 'category', + label: 'Concepts', + collapsed: false, + items: [{ type: 'autogenerated', dirName: 'concepts' }], + }, + { + type: 'category', + label: 'How-To Guides', + collapsed: true, + items: [{ type: 'autogenerated', dirName: 'how-to' }], + }, + { + type: 'category', + label: 'Tutorials', + collapsed: false, + items: [{ type: 'autogenerated', dirName: 'tutorials' }], + }, + { + type: 'category', + label: 'Reference', + collapsed: true, + items: [{ type: 'autogenerated', dirName: 'reference' }], + }, + ], + apiSidebar: [ + { + type: 'autogenerated', + dirName: 'api', + }, + ], +}; + +module.exports = sidebars; diff --git a/website/src/css/custom.css b/website/src/css/custom.css new file mode 100644 index 00000000..9b1e6f3a --- /dev/null +++ b/website/src/css/custom.css @@ -0,0 +1,27 @@ +/** + * Default Docusaurus Infima overrides for tethysext-atcore. + * Keep this minimal; later agents can customize as needed. + */ + +:root { + --ifm-color-primary: #2e7da6; + --ifm-color-primary-dark: #296f95; + --ifm-color-primary-darker: #27698c; + --ifm-color-primary-darkest: #205774; + --ifm-color-primary-light: #338bb7; + --ifm-color-primary-lighter: #3a93bf; + --ifm-color-primary-lightest: #57a3c9; + --ifm-code-font-size: 95%; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); +} + +[data-theme='dark'] { + --ifm-color-primary: #5fb4d6; + --ifm-color-primary-dark: #45a7cf; + --ifm-color-primary-darker: #38a0cb; + --ifm-color-primary-darkest: #2c84a8; + --ifm-color-primary-light: #79c1dd; + --ifm-color-primary-lighter: #86c8e0; + --ifm-color-primary-lightest: #aedaea; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); +} diff --git a/website/static/.nojekyll b/website/static/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/website/static/img/.gitkeep b/website/static/img/.gitkeep new file mode 100644 index 00000000..e69de29b